diff options
author | Louis Pilfold <louis@lpil.uk> | 2021-07-13 10:23:17 +0100 |
---|---|---|
committer | Louis Pilfold <louis@lpil.uk> | 2021-07-13 11:06:58 +0100 |
commit | fa93398d805ba13a44d26b4ccb83ef3f9edf1d01 (patch) | |
tree | fd12db4417937e42232cb8db43e2fd0f5b875324 | |
parent | 3e07ebbc721bd55d111c4e48af76dc8243e0149b (diff) | |
download | gleam_stdlib-fa93398d805ba13a44d26b4ccb83ef3f9edf1d01.tar.gz gleam_stdlib-fa93398d805ba13a44d26b4ccb83ef3f9edf1d01.zip |
JavaScript test runner
53 files changed, 10796 insertions, 10583 deletions
diff --git a/bin/run-tests.js b/bin/run-tests.js new file mode 100644 index 0000000..46e7963 --- /dev/null +++ b/bin/run-tests.js @@ -0,0 +1,38 @@ +import { opendir } from "fs/promises"; + +const dir = "gen/javascript/gleam/"; + +async function main() { + console.log("Running tests..."); + + let passes = 0; + let failures = 0; + + for await (let entry of await opendir(dir)) { + if (!entry.name.endsWith("_test.js")) continue; + let path = "../" + dir + entry.name; + process.stdout.write("\ngleam/" + entry.name.slice(0, -3) + ":\n "); + let module = await import(path); + + for (let fnName of Object.keys(module)) { + if (!fnName.endsWith("_test")) continue; + try { + module[fnName](); + process.stdout.write("✨"); + passes++; + } catch (error) { + process.stdout.write(`❌ ${fnName}: ${error}\n `); + failures++; + } + } + } + + console.log(` + +${passes + failures} tests +${passes} passes +${failures} failures`); + process.exit(failures ? 1 : 0); +} + +main(); diff --git a/bin/test.sh b/bin/test.sh new file mode 100644 index 0000000..777c50b --- /dev/null +++ b/bin/test.sh @@ -0,0 +1,7 @@ +set -eu + +OUT=gen/javascript +rm -fr $OUT +gleam compile-package --name gleam_stdlib --target javascript --src src --test test --out $OUT +cp src/*.js $OUT/ +node bin/run-tests.js diff --git a/package.json b/package.json new file mode 100644 index 0000000..0e087bf --- /dev/null +++ b/package.json @@ -0,0 +1,6 @@ +{ + "type": "module", + "scripts": { + "test": "sh bin/test.sh" + } +} diff --git a/src/gleam/atom.gleam b/src/gleam/atom.gleam index e85f5d5..2d96053 100644 --- a/src/gleam/atom.gleam +++ b/src/gleam/atom.gleam @@ -1,62 +1,64 @@ -/// Atom is a special string-like data-type that is most commonly used for -/// interfacing with code written in other BEAM languages such as Erlang and -/// Elixir. It is preferable to define your own custom types to use instead of -/// atoms where possible. -/// -/// Atoms are not used much in typical Gleam code! -/// -/// ## Creating atoms -/// -/// We can create atoms with the the [`create_from_string`](#create_from_string) -/// function, though we must be careful when doing so as atoms are never -/// garbage collected. If we create too many atoms (for example, if we convert -/// user input into atoms) we may hit the max limit of atoms and cause the -/// virtual machine to crash. -/// -pub external type Atom +if erlang { + /// Atom is a special string-like data-type that is most commonly used for + /// interfacing with code written in other BEAM languages such as Erlang and + /// Elixir. It is preferable to define your own custom types to use instead of + /// atoms where possible. + /// + /// Atoms are not used much in typical Gleam code! + /// + /// ## Creating atoms + /// + /// We can create atoms with the the [`create_from_string`](#create_from_string) + /// function, though we must be careful when doing so as atoms are never + /// garbage collected. If we create too many atoms (for example, if we convert + /// user input into atoms) we may hit the max limit of atoms and cause the + /// virtual machine to crash. + /// + pub external type Atom -/// An error returned when no atom is found in the virtual machine's atom table -/// for a given string when calling the [`from_string`](#from_string) function. -pub type FromStringError { - AtomNotLoaded -} + /// An error returned when no atom is found in the virtual machine's atom table + /// for a given string when calling the [`from_string`](#from_string) function. + pub type FromStringError { + AtomNotLoaded + } -/// Finds an existing Atom for the given String. -/// -/// If no atom is found in the virtual machine's atom table for the String then -/// an error is returned. -/// -/// ## Examples -/// -/// > from_string("ok") -/// Ok(create_from_string("ok")) -/// -/// > from_string("some_new_atom") -/// Error(AtomNotLoaded) -/// -pub external fn from_string(String) -> Result(Atom, FromStringError) = - "gleam_stdlib" "atom_from_string" + /// Finds an existing Atom for the given String. + /// + /// If no atom is found in the virtual machine's atom table for the String then + /// an error is returned. + /// + /// ## Examples + /// + /// > from_string("ok") + /// Ok(create_from_string("ok")) + /// + /// > from_string("some_new_atom") + /// Error(AtomNotLoaded) + /// + pub external fn from_string(String) -> Result(Atom, FromStringError) = + "gleam_stdlib" "atom_from_string" -/// Creates an atom from a string, inserting a new value into the virtual -/// machine's atom table if an atom does not already exist for the given -/// string. -/// -/// We must be careful when using this function as there is a limit to the -/// number of atom that can fit in the virtual machine's atom table. Never -/// convert user input into atoms as filling the atom table will cause the -/// virtual machine to crash! -/// -pub external fn create_from_string(String) -> Atom = - "gleam_stdlib" "atom_create_from_string" + /// Creates an atom from a string, inserting a new value into the virtual + /// machine's atom table if an atom does not already exist for the given + /// string. + /// + /// We must be careful when using this function as there is a limit to the + /// number of atom that can fit in the virtual machine's atom table. Never + /// convert user input into atoms as filling the atom table will cause the + /// virtual machine to crash! + /// + pub external fn create_from_string(String) -> Atom = + "gleam_stdlib" "atom_create_from_string" -/// Retuns a `String` corresponding to the text representation of the given -/// `Atom`. -/// -/// ## Examples -/// -/// > let ok_atom = create_from_string("ok") -/// > to_string(ok_atom) -/// "ok" -/// -pub external fn to_string(Atom) -> String = - "gleam_stdlib" "atom_to_string" + /// Retuns a `String` corresponding to the text representation of the given + /// `Atom`. + /// + /// ## Examples + /// + /// > let ok_atom = create_from_string("ok") + /// > to_string(ok_atom) + /// "ok" + /// + pub external fn to_string(Atom) -> String = + "gleam_stdlib" "atom_to_string" +} diff --git a/src/gleam/base.gleam b/src/gleam/base.gleam index c1c3824..5ca741e 100644 --- a/src/gleam/base.gleam +++ b/src/gleam/base.gleam @@ -1,41 +1,43 @@ -import gleam/bit_string.{BitString} -import gleam/string +if erlang { + import gleam/bit_string.{BitString} + import gleam/string -external fn erl_encode64(BitString) -> String = - "base64" "encode" + external fn erl_encode64(BitString) -> String = + "base64" "encode" -external fn erl_decode64(String) -> Result(BitString, Nil) = - "gleam_stdlib" "base_decode64" + external fn erl_decode64(String) -> Result(BitString, Nil) = + "gleam_stdlib" "base_decode64" -/// Encodes a BitString into a base 64 encoded string. -pub fn encode64(input: BitString, padding: Bool) -> String { - let encoded = erl_encode64(input) - case padding { - True -> encoded - False -> string.replace(encoded, "=", "") + /// Encodes a BitString into a base 64 encoded string. + pub fn encode64(input: BitString, padding: Bool) -> String { + let encoded = erl_encode64(input) + case padding { + True -> encoded + False -> string.replace(encoded, "=", "") + } } -} -/// Decodes a base 64 encoded string into a BitString. -pub fn decode64(encoded: String) -> Result(BitString, Nil) { - let padded = case bit_string.byte_size(bit_string.from_string(encoded)) % 4 { - 0 -> encoded - n -> string.append(encoded, string.repeat("=", 4 - n)) + /// Decodes a base 64 encoded string into a BitString. + pub fn decode64(encoded: String) -> Result(BitString, Nil) { + let padded = case bit_string.byte_size(bit_string.from_string(encoded)) % 4 { + 0 -> encoded + n -> string.append(encoded, string.repeat("=", 4 - n)) + } + erl_decode64(padded) } - erl_decode64(padded) -} -/// Encodes a BitString into a base 64 encoded string with URL and filename safe alphabet. -pub fn url_encode64(input: BitString, padding: Bool) -> String { - encode64(input, padding) - |> string.replace("+", "-") - |> string.replace("/", "_") -} + /// Encodes a BitString into a base 64 encoded string with URL and filename safe alphabet. + pub fn url_encode64(input: BitString, padding: Bool) -> String { + encode64(input, padding) + |> string.replace("+", "-") + |> string.replace("/", "_") + } -/// Decodes a base 64 encoded string with URL and filename safe alphabet into a BitString. -pub fn url_decode64(encoded: String) -> Result(BitString, Nil) { - encoded - |> string.replace("-", "+") - |> string.replace("_", "/") - |> decode64() + /// Decodes a base 64 encoded string with URL and filename safe alphabet into a BitString. + pub fn url_decode64(encoded: String) -> Result(BitString, Nil) { + encoded + |> string.replace("-", "+") + |> string.replace("_", "/") + |> decode64() + } } diff --git a/src/gleam/bit_builder.gleam b/src/gleam/bit_builder.gleam index d69fea5..738c79c 100644 --- a/src/gleam/bit_builder.gleam +++ b/src/gleam/bit_builder.gleam @@ -1,102 +1,107 @@ -import gleam/bit_string.{BitString} -import gleam/string_builder.{StringBuilder} +if erlang { + import gleam/bit_string.{BitString} + import gleam/string_builder.{StringBuilder} -/// BitBuilder is a type used for efficiently concatenating bits to create bit -/// strings. -/// -/// If we append one bit string to another the bit strings must be copied to a -/// new location in memory so that they can sit together. This behaviour -/// enables efficient reading of the string but copying can be expensive, -/// especially if we want to join many bit strings together. -/// -/// BitBuilder is different in that it can be joined together in constant -/// time using minimal memory, and then can be efficiently converted to a -/// bit string using the `to_bit_string` function. -/// -pub external type BitBuilder + /// BitBuilder is a type used for efficiently concatenating bits to create bit + /// strings. + /// + /// If we append one bit string to another the bit strings must be copied to a + /// new location in memory so that they can sit together. This behaviour + /// enables efficient reading of the string but copying can be expensive, + /// especially if we want to join many bit strings together. + /// + /// BitBuilder is different in that it can be joined together in constant + /// time using minimal memory, and then can be efficiently converted to a + /// bit string using the `to_bit_string` function. + /// + pub external type BitBuilder -/// Prepends a bit string to the start of a builder. -/// -/// Runs in constant time. -/// -pub external fn prepend(to: BitBuilder, prefix: BitString) -> BitBuilder = - "gleam_stdlib" "iodata_prepend" + /// Prepends a bit string to the start of a builder. + /// + /// Runs in constant time. + /// + pub external fn prepend(to: BitBuilder, prefix: BitString) -> BitBuilder = + "gleam_stdlib" "iodata_prepend" -/// 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" + /// 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" -/// 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" + /// 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" -/// Appends a builder onto the end of another. -/// -/// Runs in constant time. -/// -pub external fn append_builder(to: BitBuilder, suffix: BitBuilder) -> BitBuilder = - "gleam_stdlib" "iodata_append" + /// Appends a builder onto the end of another. + /// + /// Runs in constant time. + /// + pub external fn append_builder( + to: BitBuilder, + suffix: BitBuilder, + ) -> BitBuilder = + "gleam_stdlib" "iodata_append" -/// Prepends a string onto the start of a builder. -/// -/// Runs in constant time. -/// -pub external fn prepend_string(to: BitBuilder, prefix: String) -> BitBuilder = - "gleam_stdlib" "iodata_prepend" + /// Prepends a string onto the start of a builder. + /// + /// Runs in constant time. + /// + pub external fn prepend_string(to: BitBuilder, prefix: String) -> BitBuilder = + "gleam_stdlib" "iodata_prepend" -/// 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" + /// 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" -/// Joins a list of builders into a single builders. -/// -/// Runs in constant time. -/// -pub external fn concat(List(BitBuilder)) -> BitBuilder = - "gleam_stdlib" "identity" + /// Joins a list of builders into a single builders. + /// + /// Runs in constant time. + /// + pub external fn concat(List(BitBuilder)) -> BitBuilder = + "gleam_stdlib" "identity" -/// Creates a new builder from a string. -/// -/// Runs in constant time. -/// -pub external fn from_string(String) -> BitBuilder = - "gleam_stdlib" "wrap_list" + /// Creates a new builder from a string. + /// + /// Runs in constant time. + /// + pub external fn from_string(String) -> BitBuilder = + "gleam_stdlib" "wrap_list" -/// Creates a new builder from a string builder. -/// -/// Runs in constant time. -/// -pub external fn from_string_builder(StringBuilder) -> BitBuilder = - "gleam_stdlib" "identity" + /// Creates a new builder from a string builder. + /// + /// Runs in constant time. + /// + pub external fn from_string_builder(StringBuilder) -> BitBuilder = + "gleam_stdlib" "identity" -/// Creates a new builder from a bit string. -/// -/// Runs in constant time. -/// -pub external fn from_bit_string(BitString) -> BitBuilder = - "gleam_stdlib" "wrap_list" + /// Creates a new builder from a bit string. + /// + /// Runs in constant time. + /// + pub external fn from_bit_string(BitString) -> BitBuilder = + "gleam_stdlib" "wrap_list" -/// Turns an builder into a bit string. -/// -/// This function is implemented natively by the virtual machine and is highly -/// optimised. -/// -pub external fn to_bit_string(BitBuilder) -> BitString = - "erlang" "list_to_bitstring" + /// Turns an builder into a bit string. + /// + /// This function is implemented natively by the virtual machine and is highly + /// optimised. + /// + pub external fn to_bit_string(BitBuilder) -> BitString = + "erlang" "list_to_bitstring" -/// Returns the size of the builder's content in bytes. -/// -pub external fn byte_size(BitBuilder) -> Int = - "erlang" "iolist_size" + /// Returns the size of the builder's content in bytes. + /// + pub external fn byte_size(BitBuilder) -> Int = + "erlang" "iolist_size" +} diff --git a/src/gleam/bit_string.gleam b/src/gleam/bit_string.gleam index 4e10268..835ff7c 100644 --- a/src/gleam/bit_string.gleam +++ b/src/gleam/bit_string.gleam @@ -2,77 +2,79 @@ //// The BitString type should be used instead of a String type when not utf8 //// encoded. -pub type BitString = - BitString +if erlang { + pub type BitString = + BitString -/// Converts a UTF-8 String type into a raw BitString type. -/// -pub external fn from_string(String) -> BitString = - "gleam_stdlib" "identity" + /// Converts a UTF-8 String type into a raw BitString type. + /// + pub external fn from_string(String) -> BitString = + "gleam_stdlib" "identity" -/// Returns an integer which is the number of bytes in the bit string. -/// -pub external fn byte_size(BitString) -> Int = - "erlang" "byte_size" + /// Returns an integer which is the number of bytes in the bit string. + /// + pub external fn byte_size(BitString) -> Int = + "erlang" "byte_size" -/// Creates a new bit string by joining two binaries. -/// -/// ## Examples -/// -/// > append(to: from_string("butter"), suffix: from_string("fly")) -/// from_string("butterfly") -/// -pub external fn append(first: BitString, second: BitString) -> BitString = - "gleam_stdlib" "bit_string_append" + /// Creates a new bit string by joining two binaries. + /// + /// ## Examples + /// + /// > append(to: from_string("butter"), suffix: from_string("fly")) + /// from_string("butterfly") + /// + pub external fn append(first: BitString, second: BitString) -> BitString = + "gleam_stdlib" "bit_string_append" -/// Extracts part of a bit string. -/// -/// BitString part will start at given position and continue up to specified -/// length. -/// A negative length can be used to extract bytes at the end of a bit string. -/// -pub external fn part( - string: BitString, - position: Int, - length: Int, -) -> Result(BitString, Nil) = - "gleam_stdlib" "bit_string_part_" + /// Extracts part of a bit string. + /// + /// BitString part will start at given position and continue up to specified + /// length. + /// A negative length can be used to extract bytes at the end of a bit string. + /// + pub external fn part( + string: BitString, + position: Int, + length: Int, + ) -> Result(BitString, Nil) = + "gleam_stdlib" "bit_string_part_" -/// Converts an integer to unsigned 32 bits. -/// -/// Returns an error if integer is less than zero or equal to or larger than -/// 2^32. -/// -pub external fn int_to_u32(Int) -> Result(BitString, Nil) = - "gleam_stdlib" "bit_string_int_to_u32" + /// Converts an integer to unsigned 32 bits. + /// + /// Returns an error if integer is less than zero or equal to or larger than + /// 2^32. + /// + pub external fn int_to_u32(Int) -> Result(BitString, Nil) = + "gleam_stdlib" "bit_string_int_to_u32" -/// Converts unsigned 32 bits to an integer. -/// -/// Returns an error if the bit string is not 32 bits in length. -/// -pub external fn int_from_u32(BitString) -> Result(Int, Nil) = - "gleam_stdlib" "bit_string_int_from_u32" + /// Converts unsigned 32 bits to an integer. + /// + /// Returns an error if the bit string is not 32 bits in length. + /// + pub external fn int_from_u32(BitString) -> Result(Int, Nil) = + "gleam_stdlib" "bit_string_int_from_u32" -/// Tests to see whether a bit string is valid UTF-8. -/// -pub fn is_utf8(bits: BitString) -> Bool { - case bits { - <<>> -> True - <<_:utf8, rest:binary>> -> is_utf8(rest) - _ -> False + /// Tests to see whether a bit string is valid UTF-8. + /// + pub fn is_utf8(bits: BitString) -> Bool { + case bits { + <<>> -> True + <<_:utf8, rest:binary>> -> is_utf8(rest) + _ -> False + } } -} -external fn unsafe_to_string(BitString) -> String = - "gleam_stdlib" "identity" + external fn unsafe_to_string(BitString) -> String = + "gleam_stdlib" "identity" -/// Converts a bit string to a string. -/// -/// Returns an error if the bit string is invalid UTF-8 data. -/// -pub fn to_string(bits: BitString) -> Result(String, Nil) { - case is_utf8(bits) { - True -> Ok(unsafe_to_string(bits)) - False -> Error(Nil) + /// Converts a bit string to a string. + /// + /// Returns an error if the bit string is invalid UTF-8 data. + /// + pub fn to_string(bits: BitString) -> Result(String, Nil) { + case is_utf8(bits) { + True -> Ok(unsafe_to_string(bits)) + False -> Error(Nil) + } } } diff --git a/src/gleam/bool.gleam b/src/gleam/bool.gleam index 684aa2f..e25f7b5 100644 --- a/src/gleam/bool.gleam +++ b/src/gleam/bool.gleam @@ -1,205 +1,207 @@ -import gleam/order.{Order} +if erlang { + import gleam/order.{Order} -/// A type with two possible values, True and False. Used to indicate whether -/// things are... true or false! -/// -/// Often is it clearer and offers more type safety to define a custom type -/// than to use Bool. For example, rather than having a `is_teacher: Bool` -/// field consider having a `role: SchoolRole` field where SchoolRole is a custom -/// type that can be either Student or Teacher. -/// -pub type Bool = - Bool + /// A type with two possible values, True and False. Used to indicate whether + /// things are... true or false! + /// + /// Often is it clearer and offers more type safety to define a custom type + /// than to use Bool. For example, rather than having a `is_teacher: Bool` + /// field consider having a `role: SchoolRole` field where SchoolRole is a custom + /// type that can be either Student or Teacher. + /// + pub type Bool = + Bool -/// Returns the opposite bool value. -/// -/// This is the same as the `!` or `not` operators in some other languages. -/// -/// ## Examples -/// -/// > negate(True) -/// False -/// -/// > negate(False) -/// True -/// -pub fn negate(bool: Bool) -> Bool { - case bool { - True -> False - False -> True + /// Returns the opposite bool value. + /// + /// This is the same as the `!` or `not` operators in some other languages. + /// + /// ## Examples + /// + /// > negate(True) + /// False + /// + /// > negate(False) + /// True + /// + pub fn negate(bool: Bool) -> Bool { + case bool { + True -> False + False -> True + } } -} -/// Returns the nor of two bools -/// -/// ## Examples -/// -/// > nor(False, False) -/// True -/// -/// > nor(False, True) -/// False -/// -/// > nor(True, False) -/// False -/// -/// > nor(True, True) -/// False -/// -pub fn nor(a: Bool, b: Bool) -> Bool { - case a, b { - False, False -> True - False, True -> False - True, False -> False - True, True -> False + /// Returns the nor of two bools + /// + /// ## Examples + /// + /// > nor(False, False) + /// True + /// + /// > nor(False, True) + /// False + /// + /// > nor(True, False) + /// False + /// + /// > nor(True, True) + /// False + /// + pub fn nor(a: Bool, b: Bool) -> Bool { + case a, b { + False, False -> True + False, True -> False + True, False -> False + True, True -> False + } } -} -/// Returns the nand of two bools -/// -/// ## Examples -/// -/// > nand(False, False) -/// True -/// -/// > nand(False, True) -/// True -/// -/// > nand(True, False) -/// True -/// -/// > nand(True, True) -/// False -/// -pub fn nand(a: Bool, b: Bool) -> Bool { - case a, b { - False, False -> True - False, True -> True - True, False -> True - True, True -> False + /// Returns the nand of two bools + /// + /// ## Examples + /// + /// > nand(False, False) + /// True + /// + /// > nand(False, True) + /// True + /// + /// > nand(True, False) + /// True + /// + /// > nand(True, True) + /// False + /// + pub fn nand(a: Bool, b: Bool) -> Bool { + case a, b { + False, False -> True + False, True -> True + True, False -> True + True, True -> False + } } -} -/// Returns the exclusive or of two bools -/// -/// ## Examples -/// -/// > exclusive_or(False, False) -/// False -/// -/// > exclusive_or(False, True) -/// True -/// -/// > exclusive_or(True, False) -/// True -/// -/// > exclusive_or(True, True) -/// False -/// -pub fn exclusive_or(a: Bool, b: Bool) -> Bool { - case a, b { - False, False -> False - False, True -> True - True, False -> True - True, True -> False + /// Returns the exclusive or of two bools + /// + /// ## Examples + /// + /// > exclusive_or(False, False) + /// False + /// + /// > exclusive_or(False, True) + /// True + /// + /// > exclusive_or(True, False) + /// True + /// + /// > exclusive_or(True, True) + /// False + /// + pub fn exclusive_or(a: Bool, b: Bool) -> Bool { + case a, b { + False, False -> False + False, True -> True + True, False -> True + True, True -> False + } } -} -/// Returns the exclusive nor of two bools -/// -/// ## Examples -/// -/// > exclusive_nor(False, False) -/// True -/// -/// > exclusive_nor(False, True) -/// False -/// -/// > exclusive_nor(True, False) -/// False -/// -/// > exclusive_nor(True, True) -/// True -/// -pub fn exclusive_nor(a: Bool, b: Bool) -> Bool { - case a, b { - False, False -> True - False, True -> False - True, False -> False - True, True -> True + /// Returns the exclusive nor of two bools + /// + /// ## Examples + /// + /// > exclusive_nor(False, False) + /// True + /// + /// > exclusive_nor(False, True) + /// False + /// + /// > exclusive_nor(True, False) + /// False + /// + /// > exclusive_nor(True, True) + /// True + /// + pub fn exclusive_nor(a: Bool, b: Bool) -> Bool { + case a, b { + False, False -> True + False, True -> False + True, False -> False + True, True -> True + } } -} -/// Compares two bools and returns the first values Order to the second. -/// -/// ## Examples -/// -/// > import gleam/order -/// > compare(True, False) -/// order.Gt -/// -pub fn compare(a: Bool, with b: Bool) -> Order { - case a, b { - True, True -> order.Eq - True, False -> order.Gt - False, False -> order.Eq - False, True -> order.Lt + /// Compares two bools and returns the first values Order to the second. + /// + /// ## Examples + /// + /// > import gleam/order + /// > compare(True, False) + /// order.Gt + /// + pub fn compare(a: Bool, with b: Bool) -> Order { + case a, b { + True, True -> order.Eq + True, False -> order.Gt + False, False -> order.Eq + False, True -> order.Lt + } } -} -/// Returns True if either bool value is True. -/// -/// ## Examples -/// -/// > max(True, False) -/// True -/// -/// > max(False, True) -/// True -/// -/// > max(False, False) -/// False -/// -pub fn max(a: Bool, b: Bool) -> Bool { - case a { - True -> True - False -> b + /// Returns True if either bool value is True. + /// + /// ## Examples + /// + /// > max(True, False) + /// True + /// + /// > max(False, True) + /// True + /// + /// > max(False, False) + /// False + /// + pub fn max(a: Bool, b: Bool) -> Bool { + case a { + True -> True + False -> b + } } -} -/// Returns False if either bool value is False. -/// -/// ## Examples -/// -/// > max(True, False) -/// False -/// -/// > max(False, True) -/// False -/// -/// > max(False, False) -/// False -/// -pub fn min(a: Bool, b: Bool) -> Bool { - case a { - False -> False - True -> b + /// Returns False if either bool value is False. + /// + /// ## Examples + /// + /// > max(True, False) + /// False + /// + /// > max(False, True) + /// False + /// + /// > max(False, False) + /// False + /// + pub fn min(a: Bool, b: Bool) -> Bool { + case a { + False -> False + True -> b + } } -} -/// Returns a numeric representation of the given bool. -/// -/// ## Examples -/// -/// > to_int(True) -/// 1 -/// -/// > to_int(False) -/// 0 -/// -pub fn to_int(bool: Bool) -> Int { - case bool { - False -> 0 - True -> 1 + /// Returns a numeric representation of the given bool. + /// + /// ## Examples + /// + /// > to_int(True) + /// 1 + /// + /// > to_int(False) + /// 0 + /// + pub fn to_int(bool: Bool) -> Int { + case bool { + False -> 0 + True -> 1 + } } } diff --git a/src/gleam/dynamic.gleam b/src/gleam/dynamic.gleam index 162045f..be2c9b7 100644 --- a/src/gleam/dynamic.gleam +++ b/src/gleam/dynamic.gleam @@ -1,641 +1,646 @@ -import gleam/atom -import gleam/bit_string.{BitString} -import gleam/list -import gleam/map.{Map} -import gleam/option.{None, Option, Some} -import gleam/result -import gleam/string_builder - -/// `Dynamic` data is data that we don't know the type of yet. -/// We likely get data like this from interop with Erlang, or from -/// IO with the outside world. -pub external type Dynamic - -pub type Decoder(t) = - fn(Dynamic) -> Result(t, String) - -/// Converts any Gleam data into `Dynamic` data. -/// -pub external fn from(a) -> Dynamic = - "gleam_stdlib" "identity" - -/// Unsafely casts a Dynamic value into any other type. -/// -/// This is an escape hatch for the type system that may be useful when wrapping -/// native Erlang APIs. It is to be used as a last measure only! -/// -/// If you can avoid using this function, do! -/// -pub external fn unsafe_coerce(Dynamic) -> a = - "gleam_stdlib" "identity" - -/// Checks to see whether a Dynamic value is a bit_string, and return the bit_string if -/// it is. -/// -/// ## Examples -/// -/// > bit_string(from("Hello")) == bit_string.from_string("Hello") -/// True -/// -/// > bit_string(from(123)) -/// Error("Expected a BitString, got `123`") -/// -pub external fn bit_string(from: Dynamic) -> Result(BitString, String) = - "gleam_stdlib" "decode_bit_string" - -/// Checks to see whether a Dynamic value is a string, and return the string if -/// it is. -/// -/// ## Examples -/// -/// > string(from("Hello")) -/// Ok("Hello") -/// -/// > string(from(123)) -/// Error("Expected a String, got `123`") -/// -pub fn string(from: Dynamic) -> Result(String, String) { - bit_string(from) - |> result.then(fn(raw) { - case bit_string.to_string(raw) { - Ok(string) -> Ok(string) - Error(Nil) -> Error("Expected a string, got a bit_string") - } - }) -} +if erlang { + import gleam/atom + import gleam/bit_string.{BitString} + import gleam/list + import gleam/map.{Map} + import gleam/option.{None, Option, Some} + import gleam/result + import gleam/string_builder + + /// `Dynamic` data is data that we don't know the type of yet. + /// We likely get data like this from interop with Erlang, or from + /// IO with the outside world. + pub external type Dynamic + + pub type Decoder(t) = + fn(Dynamic) -> Result(t, String) + + /// Converts any Gleam data into `Dynamic` data. + /// + pub external fn from(a) -> Dynamic = + "gleam_stdlib" "identity" + + /// Unsafely casts a Dynamic value into any other type. + /// + /// This is an escape hatch for the type system that may be useful when wrapping + /// native Erlang APIs. It is to be used as a last measure only! + /// + /// If you can avoid using this function, do! + /// + pub external fn unsafe_coerce(Dynamic) -> a = + "gleam_stdlib" "identity" + + /// Checks to see whether a Dynamic value is a bit_string, and return the bit_string if + /// it is. + /// + /// ## Examples + /// + /// > bit_string(from("Hello")) == bit_string.from_string("Hello") + /// True + /// + /// > bit_string(from(123)) + /// Error("Expected a BitString, got `123`") + /// + pub external fn bit_string(from: Dynamic) -> Result(BitString, String) = + "gleam_stdlib" "decode_bit_string" + + /// Checks to see whether a Dynamic value is a string, and return the string if + /// it is. + /// + /// ## Examples + /// + /// > string(from("Hello")) + /// Ok("Hello") + /// + /// > string(from(123)) + /// Error("Expected a String, got `123`") + /// + pub fn string(from: Dynamic) -> Result(String, String) { + bit_string(from) + |> result.then(fn(raw) { + case bit_string.to_string(raw) { + Ok(string) -> Ok(string) + Error(Nil) -> Error("Expected a string, got a bit_string") + } + }) + } -/// Checks to see whether a Dynamic value is an int, and return the int if it -/// is. -/// -/// ## Examples -/// -/// > int(from(123)) -/// Ok(123) -/// -/// > int(from("Hello")) -/// Error("Expected an Int, got `\"Hello World\"`") -/// -pub external fn int(from: Dynamic) -> Result(Int, String) = - "gleam_stdlib" "decode_int" - -/// Checks to see whether a Dynamic value is an float, and return the float if -/// it is. -/// -/// ## Examples -/// -/// > float(from(2.0)) -/// Ok(2.0) -/// -/// > float(from(123)) -/// Error("Expected a Float, got `123`") -/// -pub external fn float(from: Dynamic) -> Result(Float, String) = - "gleam_stdlib" "decode_float" - -/// Checks to see whether a Dynamic value is an atom, and return the atom if -/// it is. -/// -/// ## Examples -/// -/// > import gleam/atom -/// > atom(from(atom.create_from_string("hello"))) -/// OK("hello") -/// -/// > atom(from(123)) -/// Error("Expected an Atom, got `123`") -/// -pub external fn atom(from: Dynamic) -> Result(atom.Atom, String) = - "gleam_stdlib" "decode_atom" - -/// Checks to see whether a Dynamic value is an bool, and return the bool if -/// it is. -/// -/// ## Examples -/// -/// > bool(from(True)) -/// Ok(True) -/// -/// > bool(from(123)) -/// Error("Expected a Bool, got `123`") -/// -pub external fn bool(from: Dynamic) -> Result(Bool, String) = - "gleam_stdlib" "decode_bool" - -/// Checks to see whether a Dynamic value is a function that takes no arguments, -/// and return the function if it is. -/// -/// ## Examples -/// -/// > import gleam/result -/// > let f = fn() { 1 } -/// > thunk(from(f)) |> result.is_ok -/// True -/// -/// > thunk(from(123)) -/// Error("Expected a zero arity function, got `123`") -/// -pub external fn thunk(from: Dynamic) -> Result(fn() -> Dynamic, String) = - "gleam_stdlib" "decode_thunk" - -/// Checks to see whether a Dynamic value is a list, and return the list if it -/// is. -/// -/// If you wish to decode all the elements in the list use the `typed_list` -/// instead. -/// -/// ## Examples -/// -/// > list(from(["a", "b", "c"])) -/// Ok([from("a"), from("b"), from("c")]) -/// -/// > list(1) -/// Error("Expected an Int, got a binary") -/// -pub external fn list(from: Dynamic) -> Result(List(Dynamic), String) = - "gleam_stdlib" "decode_list" - -/// Checks to see whether a Dynamic value is a result, and return the result if -/// it is -/// -/// ## Examples -/// -/// > result(from(Ok(1))) -/// Ok(Ok(from(1))) -/// -/// > result(from(Error("boom"))) -/// Ok(Error(from("boom"))) -/// -/// > result(from(123)) -/// Error("Expected a 2 element tuple, got an int") -/// -pub fn result(from: Dynamic) -> Result(Result(Dynamic, Dynamic), String) { - try #(key, val) = tuple2(from) - - try tag = atom(key) - let ok_atom = atom.create_from_string("ok") - let error_atom = atom.create_from_string("error") - case tag { - tag if tag == ok_atom -> Ok(Ok(val)) - tag if tag == error_atom -> Ok(Error(val)) - tag -> - "Expected a tag of \"ok\" or \"error\", got \"" - |> string_builder.from_string - |> string_builder.append(atom.to_string(tag)) - |> string_builder.append("\"") - |> string_builder.to_string - |> Error + /// Checks to see whether a Dynamic value is an int, and return the int if it + /// is. + /// + /// ## Examples + /// + /// > int(from(123)) + /// Ok(123) + /// + /// > int(from("Hello")) + /// Error("Expected an Int, got `\"Hello World\"`") + /// + pub external fn int(from: Dynamic) -> Result(Int, String) = + "gleam_stdlib" "decode_int" + + /// Checks to see whether a Dynamic value is an float, and return the float if + /// it is. + /// + /// ## Examples + /// + /// > float(from(2.0)) + /// Ok(2.0) + /// + /// > float(from(123)) + /// Error("Expected a Float, got `123`") + /// + pub external fn float(from: Dynamic) -> Result(Float, String) = + "gleam_stdlib" "decode_float" + + /// Checks to see whether a Dynamic value is an atom, and return the atom if + /// it is. + /// + /// ## Examples + /// + /// > import gleam/atom + /// > atom(from(atom.create_from_string("hello"))) + /// OK("hello") + /// + /// > atom(from(123)) + /// Error("Expected an Atom, got `123`") + /// + pub external fn atom(from: Dynamic) -> Result(atom.Atom, String) = + "gleam_stdlib" "decode_atom" + + /// Checks to see whether a Dynamic value is an bool, and return the bool if + /// it is. + /// + /// ## Examples + /// + /// > bool(from(True)) + /// Ok(True) + /// + /// > bool(from(123)) + /// Error("Expected a Bool, got `123`") + /// + pub external fn bool(from: Dynamic) -> Result(Bool, String) = + "gleam_stdlib" "decode_bool" + + /// Checks to see whether a Dynamic value is a function that takes no arguments, + /// and return the function if it is. + /// + /// ## Examples + /// + /// > import gleam/result + /// > let f = fn() { 1 } + /// > thunk(from(f)) |> result.is_ok + /// True + /// + /// > thunk(from(123)) + /// Error("Expected a zero arity function, got `123`") + /// + pub external fn thunk(from: Dynamic) -> Result(fn() -> Dynamic, String) = + "gleam_stdlib" "decode_thunk" + + /// Checks to see whether a Dynamic value is a list, and return the list if it + /// is. + /// + /// If you wish to decode all the elements in the list use the `typed_list` + /// instead. + /// + /// ## Examples + /// + /// > list(from(["a", "b", "c"])) + /// Ok([from("a"), from("b"), from("c")]) + /// + /// > list(1) + /// Error("Expected an Int, got a binary") + /// + pub external fn list(from: Dynamic) -> Result(List(Dynamic), String) = + "gleam_stdlib" "decode_list" + + /// Checks to see whether a Dynamic value is a result, and return the result if + /// it is + /// + /// ## Examples + /// + /// > result(from(Ok(1))) + /// Ok(Ok(from(1))) + /// + /// > result(from(Error("boom"))) + /// Ok(Error(from("boom"))) + /// + /// > result(from(123)) + /// Error("Expected a 2 element tuple, got an int") + /// + pub fn result(from: Dynamic) -> Result(Result(Dynamic, Dynamic), String) { + try #(key, val) = tuple2(from) + + try tag = atom(key) + let ok_atom = atom.create_from_string("ok") + let error_atom = atom.create_from_string("error") + case tag { + tag if tag == ok_atom -> Ok(Ok(val)) + tag if tag == error_atom -> Ok(Error(val)) + tag -> + "Expected a tag of \"ok\" or \"error\", got \"" + |> string_builder.from_string + |> string_builder.append(atom.to_string(tag)) + |> string_builder.append("\"") + |> string_builder.to_string + |> Error + } } -} -/// Checks to see whether a Dynamic value is a result of a particular type, and -/// return the result if it is -/// -/// The `ok` and `error` arguments are decoders for decoding the `Ok` and -/// `Error` values of the result. -/// -/// ## Examples -/// -/// > typed_result(of: from(Ok(1)), ok: int, error: string) -/// Ok(Ok(1)) -/// -/// > typed_result(of: from(Error("boom")), ok: int, error: string) -/// Ok(Error("boom")) -/// -/// > typed_result(of: from(123), ok: int, error: string) -/// Error("Expected a 2 element tuple, got an int") -/// -pub fn typed_result( - of dynamic: Dynamic, - ok decode_ok: Decoder(a), - error decode_error: Decoder(e), -) -> Result(Result(a, e), String) { - try inner_result = result(dynamic) - - case inner_result { - Ok(raw) -> - raw - |> decode_ok - |> result.map(Ok) - Error(raw) -> - raw - |> decode_error - |> result.map(Error) + /// Checks to see whether a Dynamic value is a result of a particular type, and + /// return the result if it is + /// + /// The `ok` and `error` arguments are decoders for decoding the `Ok` and + /// `Error` values of the result. + /// + /// ## Examples + /// + /// > typed_result(of: from(Ok(1)), ok: int, error: string) + /// Ok(Ok(1)) + /// + /// > typed_result(of: from(Error("boom")), ok: int, error: string) + /// Ok(Error("boom")) + /// + /// > typed_result(of: from(123), ok: int, error: string) + /// Error("Expected a 2 element tuple, got an int") + /// + pub fn typed_result( + of dynamic: Dynamic, + ok decode_ok: Decoder(a), + error decode_error: Decoder(e), + ) -> Result(Result(a, e), String) { + try inner_result = result(dynamic) + + case inner_result { + Ok(raw) -> + raw + |> decode_ok + |> result.map(Ok) + Error(raw) -> + raw + |> decode_error + |> result.map(Error) + } } -} -/// Checks to see whether a Dynamic value is a list of a particular type, and -/// return the list if it is. -/// -/// The second argument is a decoder function used to decode the elements of -/// the list. The list is only decoded if all elements in the list can be -/// successfully decoded using this function. -/// -/// If you do not wish to decode all the elements in the list use the `list` -/// function instead. -/// -/// ## Examples -/// -/// > typed_list(from(["a", "b", "c"]), of: string) -/// Ok(["a", "b", "c"]) -/// -/// > typed_list(from([1, 2, 3]), of: string) -/// Error("Expected an Int, got a binary") -/// -/// > typed_list(from("ok"), of: string) -/// Error("Expected a List, got a binary") -/// -pub fn typed_list( - from dynamic: Dynamic, - of decoder_type: fn(Dynamic) -> Result(inner, String), -) -> Result(List(inner), String) { - dynamic - |> list - |> result.then(list.try_map(_, decoder_type)) -} + /// Checks to see whether a Dynamic value is a list of a particular type, and + /// return the list if it is. + /// + /// The second argument is a decoder function used to decode the elements of + /// the list. The list is only decoded if all elements in the list can be + /// successfully decoded using this function. + /// + /// If you do not wish to decode all the elements in the list use the `list` + /// function instead. + /// + /// ## Examples + /// + /// > typed_list(from(["a", "b", "c"]), of: string) + /// Ok(["a", "b", "c"]) + /// + /// > typed_list(from([1, 2, 3]), of: string) + /// Error("Expected an Int, got a binary") + /// + /// > typed_list(from("ok"), of: string) + /// Error("Expected a List, got a binary") + /// + pub fn typed_list( + from dynamic: Dynamic, + of decoder_type: fn(Dynamic) -> Result(inner, String), + ) -> Result(List(inner), String) { + dynamic + |> list + |> result.then(list.try_map(_, decoder_type)) + } -/// Checks to see if a Dynamic value is an Option of a particular type, and return -/// the Option if it is. -/// -/// The second argument is a decoder function used to decode the elements of -/// the list. The list is only decoded if all elements in the list can be -/// successfully decoded using this function. -/// -/// ## Examples -/// -/// > option(from("Hello"), string) -/// Ok(Some("Hello")) -/// -/// > option(from(atom.from_string("null")), string) -/// Ok(None) -/// -/// > option(from(123), string) -/// Error("Expected a bit_string, got an int") -/// -pub fn option( - from dynamic: Dynamic, - of decoder: Decoder(inner), -) -> Result(Option(inner), String) { - let Ok(null) = atom.from_string("null") - case atom(dynamic), decoder(dynamic) { - Ok(atom), _ if atom == null -> Ok(None) - _, Ok(result) -> Ok(Some(result)) - _, Error(msg) -> Error(msg) + /// Checks to see if a Dynamic value is an Option of a particular type, and return + /// the Option if it is. + /// + /// The second argument is a decoder function used to decode the elements of + /// the list. The list is only decoded if all elements in the list can be + /// successfully decoded using this function. + /// + /// ## Examples + /// + /// > option(from("Hello"), string) + /// Ok(Some("Hello")) + /// + /// > option(from(atom.from_string("null")), string) + /// Ok(None) + /// + /// > option(from(123), string) + /// Error("Expected a bit_string, got an int") + /// + pub fn option( + from dynamic: Dynamic, + of decoder: Decoder(inner), + ) -> Result(Option(inner), String) { + let Ok(null) = atom.from_string("null") + case atom(dynamic), decoder(dynamic) { + Ok(atom), _ if atom == null -> Ok(None) + _, Ok(result) -> Ok(Some(result)) + _, Error(msg) -> Error(msg) + } } -} -/// Checks to see if a Dynamic value is a map with a specific field, and return -/// the value of the field if it is. -/// -/// This will not succeed on a record. -/// -/// ## Examples -/// -/// > import gleam/map -/// > field(from(map.new("Hello", "World")), "Hello") -/// Ok(Dynamic) -/// -/// > field(from(123), "Hello") -/// Error("Expected a map with key `\"Hello\"`, got an Int") -/// -pub external fn field(from: Dynamic, named: a) -> Result(Dynamic, String) = - "gleam_stdlib" "decode_field" - -/// Checks to see if the Dynamic value is a tuple large enough to have a certain -/// index, and return the value of that index if it is. -/// -/// ## Examples -/// -/// > element(from(#(1, 2)), 0) -/// Ok(from(1)) -/// -/// > element(from(#(1, 2)), 2) -/// Error("Expected a tuple of at least 3 size, got a tuple of 2 size") -/// -/// > element(from(""), 2) -/// Error("Expected a tuple, got a binary") -/// -pub external fn element(from: Dynamic, position: Int) -> Result(Dynamic, String) = - "gleam_stdlib" "decode_element" - -/// Checks to see if the Dynamic value is a 2 element tuple. -/// -/// If you do not wish to decode all the elements in the tuple use the -/// `typed_tuple2` function instead. -/// -/// ## Examples -/// -/// > tuple2(from(#(1, 2))) -/// Ok(#(from(1), from(2))) -/// -/// > tuple2(from(#(1, 2, 3))) -/// Error("Expected a 2 element tuple") -/// -/// > tuple2(from("")) -/// Error("Expected a tuple, got a binary") -/// -pub external fn tuple2(from: Dynamic) -> Result(#(Dynamic, Dynamic), String) = - "gleam_stdlib" "decode_tuple2" - -/// Checks to see if the Dynamic value is a 2 element tuple containing two -/// specifically typed elements. -/// -/// If you wish to decode all the elements in the list use the `typed_tuple2` -/// instead. -/// -/// ## Examples -/// -/// > typed_tuple2(from(#(1, 2)), int, int) -/// Ok(#(1, 2)) -/// -/// > typed_tuple2(from(#(1, 2.0)), int, float) -/// Ok(#(1, 2.0)) -/// -/// > typed_tuple2(from(#(1, 2, 3)), int, float) -/// Error("Expected a 2 element tuple, got a 3 element tuple") -/// -/// > typed_tuple2(from(""), int, float) -/// Error("Expected a tuple, got a binary") -/// -pub fn typed_tuple2( - from tup: Dynamic, - first decode_first: Decoder(a), - second decode_second: Decoder(b), -) -> Result(#(a, b), String) { - try #(first, second) = tuple2(tup) - try a = decode_first(first) - try b = decode_second(second) - Ok(#(a, b)) -} + /// Checks to see if a Dynamic value is a map with a specific field, and return + /// the value of the field if it is. + /// + /// This will not succeed on a record. + /// + /// ## Examples + /// + /// > import gleam/map + /// > field(from(map.new("Hello", "World")), "Hello") + /// Ok(Dynamic) + /// + /// > field(from(123), "Hello") + /// Error("Expected a map with key `\"Hello\"`, got an Int") + /// + pub external fn field(from: Dynamic, named: a) -> Result(Dynamic, String) = + "gleam_stdlib" "decode_field" + + /// Checks to see if the Dynamic value is a tuple large enough to have a certain + /// index, and return the value of that index if it is. + /// + /// ## Examples + /// + /// > element(from(#(1, 2)), 0) + /// Ok(from(1)) + /// + /// > element(from(#(1, 2)), 2) + /// Error("Expected a tuple of at least 3 size, got a tuple of 2 size") + /// + /// > element(from(""), 2) + /// Error("Expected a tuple, got a binary") + /// + pub external fn element( + from: Dynamic, + position: Int, + ) -> Result(Dynamic, String) = + "gleam_stdlib" "decode_element" + + /// Checks to see if the Dynamic value is a 2 element tuple. + /// + /// If you do not wish to decode all the elements in the tuple use the + /// `typed_tuple2` function instead. + /// + /// ## Examples + /// + /// > tuple2(from(#(1, 2))) + /// Ok(#(from(1), from(2))) + /// + /// > tuple2(from(#(1, 2, 3))) + /// Error("Expected a 2 element tuple") + /// + /// > tuple2(from("")) + /// Error("Expected a tuple, got a binary") + /// + pub external fn tuple2(from: Dynamic) -> Result(#(Dynamic, Dynamic), String) = + "gleam_stdlib" "decode_tuple2" + + /// Checks to see if the Dynamic value is a 2 element tuple containing two + /// specifically typed elements. + /// + /// If you wish to decode all the elements in the list use the `typed_tuple2` + /// instead. + /// + /// ## Examples + /// + /// > typed_tuple2(from(#(1, 2)), int, int) + /// Ok(#(1, 2)) + /// + /// > typed_tuple2(from(#(1, 2.0)), int, float) + /// Ok(#(1, 2.0)) + /// + /// > typed_tuple2(from(#(1, 2, 3)), int, float) + /// Error("Expected a 2 element tuple, got a 3 element tuple") + /// + /// > typed_tuple2(from(""), int, float) + /// Error("Expected a tuple, got a binary") + /// + pub fn typed_tuple2( + from tup: Dynamic, + first decode_first: Decoder(a), + second decode_second: Decoder(b), + ) -> Result(#(a, b), String) { + try #(first, second) = tuple2(tup) + try a = decode_first(first) + try b = decode_second(second) + Ok(#(a, b)) + } -/// Checks to see if the Dynamic value is a 3 element tuple. -/// -/// If you do not wish to decode all the elements in the tuple use the -/// `typed_tuple3` function instead. -/// -/// ## Examples -/// -/// > tuple3(from(#(1, 2, 3))) -/// Ok(#(from(1), from(2), from(3))) -/// -/// > tuple3(from(#(1, 2))) -/// Error("Expected a 3 element tuple") -/// -/// > tuple3(from("")) -/// Error("Expected a tuple, got a binary") -/// -pub external fn tuple3( - from: Dynamic, -) -> Result(#(Dynamic, Dynamic, Dynamic), String) = - "gleam_stdlib" "decode_tuple3" - -/// Checks to see if the Dynamic value is a 3 element tuple containing two -/// specifically typed elements. -/// -/// If you wish to decode all the elements in the list use the `typed_tuple3` -/// instead. -/// -/// ## Examples -/// -/// > typed_tuple3(from(#(1, 2, 3)), int, int, int) -/// Ok(#(1, 2, 3)) -/// -/// > typed_tuple3(from(#(1, 2.0, "3")), int, float, string) -/// Ok(#(1, 2.0, "3")) -/// -/// > typed_tuple3(from(#(1, 2)), int, float, string) -/// Error("Expected a 3 element tuple, got a 2 element tuple") -/// -/// > typed_tuple3(from(""), int, float, string) -/// Error("Expected a tuple, got a binary") -/// -pub fn typed_tuple3( - from tup: Dynamic, - first decode_first: Decoder(a), - second decode_second: Decoder(b), - third decode_third: Decoder(c), -) -> Result(#(a, b, c), String) { - try #(first, second, third) = tuple3(tup) - try a = decode_first(first) - try b = decode_second(second) - try c = decode_third(third) - Ok(#(a, b, c)) -} + /// Checks to see if the Dynamic value is a 3 element tuple. + /// + /// If you do not wish to decode all the elements in the tuple use the + /// `typed_tuple3` function instead. + /// + /// ## Examples + /// + /// > tuple3(from(#(1, 2, 3))) + /// Ok(#(from(1), from(2), from(3))) + /// + /// > tuple3(from(#(1, 2))) + /// Error("Expected a 3 element tuple") + /// + /// > tuple3(from("")) + /// Error("Expected a tuple, got a binary") + /// + pub external fn tuple3( + from: Dynamic, + ) -> Result(#(Dynamic, Dynamic, Dynamic), String) = + "gleam_stdlib" "decode_tuple3" + + /// Checks to see if the Dynamic value is a 3 element tuple containing two + /// specifically typed elements. + /// + /// If you wish to decode all the elements in the list use the `typed_tuple3` + /// instead. + /// + /// ## Examples + /// + /// > typed_tuple3(from(#(1, 2, 3)), int, int, int) + /// Ok(#(1, 2, 3)) + /// + /// > typed_tuple3(from(#(1, 2.0, "3")), int, float, string) + /// Ok(#(1, 2.0, "3")) + /// + /// > typed_tuple3(from(#(1, 2)), int, float, string) + /// Error("Expected a 3 element tuple, got a 2 element tuple") + /// + /// > typed_tuple3(from(""), int, float, string) + /// Error("Expected a tuple, got a binary") + /// + pub fn typed_tuple3( + from tup: Dynamic, + first decode_first: Decoder(a), + second decode_second: Decoder(b), + third decode_third: Decoder(c), + ) -> Result(#(a, b, c), String) { + try #(first, second, third) = tuple3(tup) + try a = decode_first(first) + try b = decode_second(second) + try c = decode_third(third) + Ok(#(a, b, c)) + } -/// Checks to see if the Dynamic value is a 4 element tuple. -/// -/// If you do not wish to decode all the elements in the tuple use the -/// `typed_tuple4` function instead. -/// -/// ## Examples -/// -/// > tuple4(from(#(1, 2, 3, 4))) -/// Ok(#(from(1), from(2), from(3), from(4))) -/// -/// > tuple4(from(#(1, 2))) -/// Error("Expected a 4 element tuple") -/// -/// > tuple4(from("")) -/// Error("Expected a tuple, got a binary") -/// -pub external fn tuple4( - from: Dynamic, -) -> Result(#(Dynamic, Dynamic, Dynamic, Dynamic), String) = - "gleam_stdlib" "decode_tuple4" - -/// Checks to see if the Dynamic value is a 4 element tuple containing two -/// specifically typed elements. -/// -/// If you wish to decode all the elements in the list use the `typed_tuple4` -/// instead. -/// -/// ## Examples -/// -/// > typed_tuple4(from(#(1, 2, 3, 4)), int, int, int, int) -/// Ok(#(1, 2, 3, 4)) -/// -/// > typed_tuple4(from(#(1, 2.0, "3", 4)), int, float, string, int) -/// Ok(#(1, 2.0, "3", 4)) -/// -/// > typed_tuple4(from(#(1, 2)), int, float, string, int) -/// Error("Expected a 4 element tuple, got a 2 element tuple") -/// -/// > typed_tuple4(from(""), int, float, string, int) -/// Error("Expected a tuple, got a binary") -/// -pub fn typed_tuple4( - from tup: Dynamic, - first decode_first: Decoder(a), - second decode_second: Decoder(b), - third decode_third: Decoder(c), - fourth decode_fourth: Decoder(d), -) -> Result(#(a, b, c, d), String) { - try #(first, second, third, fourth) = tuple4(tup) - try a = decode_first(first) - try b = decode_second(second) - try c = decode_third(third) - try d = decode_fourth(fourth) - Ok(#(a, b, c, d)) -} + /// Checks to see if the Dynamic value is a 4 element tuple. + /// + /// If you do not wish to decode all the elements in the tuple use the + /// `typed_tuple4` function instead. + /// + /// ## Examples + /// + /// > tuple4(from(#(1, 2, 3, 4))) + /// Ok(#(from(1), from(2), from(3), from(4))) + /// + /// > tuple4(from(#(1, 2))) + /// Error("Expected a 4 element tuple") + /// + /// > tuple4(from("")) + /// Error("Expected a tuple, got a binary") + /// + pub external fn tuple4( + from: Dynamic, + ) -> Result(#(Dynamic, Dynamic, Dynamic, Dynamic), String) = + "gleam_stdlib" "decode_tuple4" + + /// Checks to see if the Dynamic value is a 4 element tuple containing two + /// specifically typed elements. + /// + /// If you wish to decode all the elements in the list use the `typed_tuple4` + /// instead. + /// + /// ## Examples + /// + /// > typed_tuple4(from(#(1, 2, 3, 4)), int, int, int, int) + /// Ok(#(1, 2, 3, 4)) + /// + /// > typed_tuple4(from(#(1, 2.0, "3", 4)), int, float, string, int) + /// Ok(#(1, 2.0, "3", 4)) + /// + /// > typed_tuple4(from(#(1, 2)), int, float, string, int) + /// Error("Expected a 4 element tuple, got a 2 element tuple") + /// + /// > typed_tuple4(from(""), int, float, string, int) + /// Error("Expected a tuple, got a binary") + /// + pub fn typed_tuple4( + from tup: Dynamic, + first decode_first: Decoder(a), + second decode_second: Decoder(b), + third decode_third: Decoder(c), + fourth decode_fourth: Decoder(d), + ) -> Result(#(a, b, c, d), String) { + try #(first, second, third, fourth) = tuple4(tup) + try a = decode_first(first) + try b = decode_second(second) + try c = decode_third(third) + try d = decode_fourth(fourth) + Ok(#(a, b, c, d)) + } -/// Checks to see if the Dynamic value is a 5 element tuple. -/// -/// If you do not wish to decode all the elements in the tuple use the -/// `typed_tuple5` function instead. -/// -/// ## Examples -/// -/// > tuple5(from(#(1, 2, 3, 4, 5))) -/// Ok(#(from(1), from(2), from(3), from(4), from(5))) -/// -/// > tuple5(from(#(1, 2))) -/// Error("Expected a 5 element tuple") -/// -/// > tuple5(from("")) -/// Error("Expected a tuple, got a binary") -/// -pub external fn tuple5( - from: Dynamic, -) -> Result(#(Dynamic, Dynamic, Dynamic, Dynamic, Dynamic), String) = - "gleam_stdlib" "decode_tuple5" - -/// Checks to see if the Dynamic value is a 5 element tuple containing two -/// specifically typed elements. -/// -/// If you wish to decode all the elements in the list use the `typed_tuple5` -/// instead. -/// -/// ## Examples -/// -/// > typed_tuple5(from(#(1, 2, 3, 4, 5)), int, int, int, int, int) -/// Ok(#(1, 2, 3, 4, 5)) -/// -/// > typed_tuple5(from(#(1, 2.0, "3", 4, 5)), int, float, string, int, int) -/// Ok(#(1, 2.0, "3", 4, 5)) -/// -/// > typed_tuple5(from(#(1, 2)), int, float, string, int, int) -/// Error("Expected a 5 element tuple, got a 2 element tuple") -/// -/// > typed_tuple5(from(""), int, float, string, int, int) -/// Error("Expected a tuple, got a binary") -/// -pub fn typed_tuple5( - from tup: Dynamic, - first decode_first: Decoder(a), - second decode_second: Decoder(b), - third decode_third: Decoder(c), - fourth decode_fourth: Decoder(d), - fifth decode_fifth: Decoder(e), -) -> Result(#(a, b, c, d, e), String) { - try #(first, second, third, fourth, fifth) = tuple5(tup) - try a = decode_first(first) - try b = decode_second(second) - try c = decode_third(third) - try d = decode_fourth(fourth) - try e = decode_fifth(fifth) - Ok(#(a, b, c, d, e)) -} + /// Checks to see if the Dynamic value is a 5 element tuple. + /// + /// If you do not wish to decode all the elements in the tuple use the + /// `typed_tuple5` function instead. + /// + /// ## Examples + /// + /// > tuple5(from(#(1, 2, 3, 4, 5))) + /// Ok(#(from(1), from(2), from(3), from(4), from(5))) + /// + /// > tuple5(from(#(1, 2))) + /// Error("Expected a 5 element tuple") + /// + /// > tuple5(from("")) + /// Error("Expected a tuple, got a binary") + /// + pub external fn tuple5( + from: Dynamic, + ) -> Result(#(Dynamic, Dynamic, Dynamic, Dynamic, Dynamic), String) = + "gleam_stdlib" "decode_tuple5" + + /// Checks to see if the Dynamic value is a 5 element tuple containing two + /// specifically typed elements. + /// + /// If you wish to decode all the elements in the list use the `typed_tuple5` + /// instead. + /// + /// ## Examples + /// + /// > typed_tuple5(from(#(1, 2, 3, 4, 5)), int, int, int, int, int) + /// Ok(#(1, 2, 3, 4, 5)) + /// + /// > typed_tuple5(from(#(1, 2.0, "3", 4, 5)), int, float, string, int, int) + /// Ok(#(1, 2.0, "3", 4, 5)) + /// + /// > typed_tuple5(from(#(1, 2)), int, float, string, int, int) + /// Error("Expected a 5 element tuple, got a 2 element tuple") + /// + /// > typed_tuple5(from(""), int, float, string, int, int) + /// Error("Expected a tuple, got a binary") + /// + pub fn typed_tuple5( + from tup: Dynamic, + first decode_first: Decoder(a), + second decode_second: Decoder(b), + third decode_third: Decoder(c), + fourth decode_fourth: Decoder(d), + fifth decode_fifth: Decoder(e), + ) -> Result(#(a, b, c, d, e), String) { + try #(first, second, third, fourth, fifth) = tuple5(tup) + try a = decode_first(first) + try b = decode_second(second) + try c = decode_third(third) + try d = decode_fourth(fourth) + try e = decode_fifth(fifth) + Ok(#(a, b, c, d, e)) + } -/// Checks to see if the Dynamic value is a 6 element tuple. -/// -/// If you do not wish to decode all the elements in the tuple use the -/// `typed_tuple6` function instead. -/// -/// ## Examples -/// -/// > tuple6(from(#(1, 2, 3, 4, 5, 6))) -/// Ok(#(from(1), from(2), from(3), from(4), from(5), from(6))) -/// -/// > tuple6(from(#(1, 2))) -/// Error("Expected a 6 element tuple") -/// -/// > tuple6(from("")) -/// Error("Expected a tuple, got a binary") -/// -pub external fn tuple6( - from: Dynamic, -) -> Result(#(Dynamic, Dynamic, Dynamic, Dynamic, Dynamic, Dynamic), String) = - "gleam_stdlib" "decode_tuple6" - -/// Checks to see if the Dynamic value is a 6 element tuple containing two -/// specifically typed elements. -/// -/// If you wish to decode all the elements in the list use the `typed_tuple6` -/// instead. -/// -/// ## Examples -/// -/// > typed_tuple6(from(#(1, 2, 3, 4, 5, 6)), int, int, int, int, int, int) -/// Ok(#(1, 2, 3, 4, 5, 6)) -/// -/// > typed_tuple6(from(#(1, 2.0, "3", 4, 5, 6)), int, float, string, int, int) -/// Ok(#(1, 2.0, "3", 4, 5, 6)) -/// -/// > typed_tuple6(from(#(1, 2)), int, float, string, int, int, int) -/// Error("Expected a 6 element tuple, got a 2 element tuple") -/// -/// > typed_tuple6(from(""), int, float, string, int, int, int) -/// Error("Expected a tuple, got a binary") -/// -pub fn typed_tuple6( - from tup: Dynamic, - first decode_first: Decoder(a), - second decode_second: Decoder(b), - third decode_third: Decoder(c), - fourth decode_fourth: Decoder(d), - fifth decode_fifth: Decoder(e), - sixth decode_sixth: Decoder(f), -) -> Result(#(a, b, c, d, e, f), String) { - try #(first, second, third, fourth, fifth, sixth) = tuple6(tup) - try a = decode_first(first) - try b = decode_second(second) - try c = decode_third(third) - try d = decode_fourth(fourth) - try e = decode_fifth(fifth) - try f = decode_sixth(sixth) - Ok(#(a, b, c, d, e, f)) -} + /// Checks to see if the Dynamic value is a 6 element tuple. + /// + /// If you do not wish to decode all the elements in the tuple use the + /// `typed_tuple6` function instead. + /// + /// ## Examples + /// + /// > tuple6(from(#(1, 2, 3, 4, 5, 6))) + /// Ok(#(from(1), from(2), from(3), from(4), from(5), from(6))) + /// + /// > tuple6(from(#(1, 2))) + /// Error("Expected a 6 element tuple") + /// + /// > tuple6(from("")) + /// Error("Expected a tuple, got a binary") + /// + pub external fn tuple6( + from: Dynamic, + ) -> Result(#(Dynamic, Dynamic, Dynamic, Dynamic, Dynamic, Dynamic), String) = + "gleam_stdlib" "decode_tuple6" + + /// Checks to see if the Dynamic value is a 6 element tuple containing two + /// specifically typed elements. + /// + /// If you wish to decode all the elements in the list use the `typed_tuple6` + /// instead. + /// + /// ## Examples + /// + /// > typed_tuple6(from(#(1, 2, 3, 4, 5, 6)), int, int, int, int, int, int) + /// Ok(#(1, 2, 3, 4, 5, 6)) + /// + /// > typed_tuple6(from(#(1, 2.0, "3", 4, 5, 6)), int, float, string, int, int) + /// Ok(#(1, 2.0, "3", 4, 5, 6)) + /// + /// > typed_tuple6(from(#(1, 2)), int, float, string, int, int, int) + /// Error("Expected a 6 element tuple, got a 2 element tuple") + /// + /// > typed_tuple6(from(""), int, float, string, int, int, int) + /// Error("Expected a tuple, got a binary") + /// + pub fn typed_tuple6( + from tup: Dynamic, + first decode_first: Decoder(a), + second decode_second: Decoder(b), + third decode_third: Decoder(c), + fourth decode_fourth: Decoder(d), + fifth decode_fifth: Decoder(e), + sixth decode_sixth: Decoder(f), + ) -> Result(#(a, b, c, d, e, f), String) { + try #(first, second, third, fourth, fifth, sixth) = tuple6(tup) + try a = decode_first(first) + try b = decode_second(second) + try c = decode_third(third) + try d = decode_fourth(fourth) + try e = decode_fifth(fifth) + try f = decode_sixth(sixth) + Ok(#(a, b, c, d, e, f)) + } -/// Checks to see if the Dynamic value is map. -/// -/// ## Examples -/// -/// > import gleam/map -/// > map(from(map.new())) -/// Ok(map.new()) -/// -/// > map(from(1)) -/// Error("Expected a 2 element tuple, got an int") -/// -/// > map(from("")) -/// Error("Expected a map, got a binary") -/// -pub external fn map(from: Dynamic) -> Result(Map(Dynamic, Dynamic), String) = - "gleam_stdlib" "decode_map" - -/// Joins multiple decoders into one. When run they will each be tried in turn -/// until one succeeds, or they all fail. -/// -/// ## Examples -/// -/// > import gleam/result -/// > let bool_or_string = any(_, of: [ -/// > string, -/// > fn(x) { result.map(bool(x), fn(_) { "a bool" }) } -/// > ]) -/// > bool_or_string(from("ok")) -/// Ok("ok") -/// -/// > bool_or_string(from(True)) -/// Ok("a bool") -/// -/// > bool_or_string(from(1)) -/// Error("Unexpected value") -/// -pub fn any( - from data: Dynamic, - of decoders: List(Decoder(t)), -) -> Result(t, String) { - decoders - |> list.find_map(fn(decoder) { decoder(data) }) - |> result.map_error(fn(_) { "Unexpected value" }) + /// Checks to see if the Dynamic value is map. + /// + /// ## Examples + /// + /// > import gleam/map + /// > map(from(map.new())) + /// Ok(map.new()) + /// + /// > map(from(1)) + /// Error("Expected a 2 element tuple, got an int") + /// + /// > map(from("")) + /// Error("Expected a map, got a binary") + /// + pub external fn map(from: Dynamic) -> Result(Map(Dynamic, Dynamic), String) = + "gleam_stdlib" "decode_map" + + /// Joins multiple decoders into one. When run they will each be tried in turn + /// until one succeeds, or they all fail. + /// + /// ## Examples + /// + /// > import gleam/result + /// > let bool_or_string = any(_, of: [ + /// > string, + /// > fn(x) { result.map(bool(x), fn(_) { "a bool" }) } + /// > ]) + /// > bool_or_string(from("ok")) + /// Ok("ok") + /// + /// > bool_or_string(from(True)) + /// Ok("a bool") + /// + /// > bool_or_string(from(1)) + /// Error("Unexpected value") + /// + pub fn any( + from data: Dynamic, + of decoders: List(Decoder(t)), + ) -> Result(t, String) { + decoders + |> list.find_map(fn(decoder) { decoder(data) }) + |> result.map_error(fn(_) { "Unexpected value" }) + } } diff --git a/src/gleam/float.gleam b/src/gleam/float.gleam index 2a646c2..ed1d3ab 100644 --- a/src/gleam/float.gleam +++ b/src/gleam/float.gleam @@ -1,228 +1,230 @@ -import gleam/string_builder -import gleam/order.{Order} - -pub type Float = - Float - -/// Attempts to parse a string as a float, returning `Error(Nil)` if it was not -/// possible. -/// -/// ## Examples -/// > parse("2.3") -/// Ok(2.3) -/// -/// > parse("ABC") -/// Error(Nil) -/// -pub external fn parse(String) -> Result(Float, Nil) = - "gleam_stdlib" "parse_float" - -/// Returns the string representation of the provided float. -/// -/// ## Examples -/// > to_string(2.3) -/// "2.3" -/// -pub fn to_string(f: Float) -> String { - f - |> string_builder.from_float - |> string_builder.to_string -} +if erlang { + import gleam/string_builder + import gleam/order.{Order} + + pub type Float = + Float + + /// Attempts to parse a string as a float, returning `Error(Nil)` if it was not + /// possible. + /// + /// ## Examples + /// > parse("2.3") + /// Ok(2.3) + /// + /// > parse("ABC") + /// Error(Nil) + /// + pub external fn parse(String) -> Result(Float, Nil) = + "gleam_stdlib" "parse_float" + + /// Returns the string representation of the provided float. + /// + /// ## Examples + /// > to_string(2.3) + /// "2.3" + /// + pub fn to_string(f: Float) -> String { + f + |> string_builder.from_float + |> string_builder.to_string + } -/// Restricts a Float between a lower and upper bound -/// -/// ## Examples -/// -/// ``` -/// > clamp(1.2, min: 1.4, max: 1.6) -/// 1.4 -/// ``` -/// -pub fn clamp(n: Float, min min_bound: Float, max max_bound: Float) -> Float { - n - |> min(max_bound) - |> max(min_bound) -} + /// Restricts a Float between a lower and upper bound + /// + /// ## Examples + /// + /// ``` + /// > clamp(1.2, min: 1.4, max: 1.6) + /// 1.4 + /// ``` + /// + pub fn clamp(n: Float, min min_bound: Float, max max_bound: Float) -> Float { + n + |> min(max_bound) + |> max(min_bound) + } -/// Compares two floats, returning an order. -/// -/// ## Examples -/// > compare(2.0, 2.3) -/// Lt -/// -pub fn compare(a: Float, with b: Float) -> Order { - case a == b { - True -> order.Eq - False -> - case a <. b { - True -> order.Lt - False -> order.Gt - } + /// Compares two floats, returning an order. + /// + /// ## Examples + /// > compare(2.0, 2.3) + /// Lt + /// + pub fn compare(a: Float, with b: Float) -> Order { + case a == b { + True -> order.Eq + False -> + case a <. b { + True -> order.Lt + False -> order.Gt + } + } } -} -/// Compares two floats, returning the smaller of the two. -/// -/// ## Examples -/// -/// > min(2.0, 2.3) -/// 2.0 -/// -pub fn min(a: Float, b: Float) -> Float { - case a <. b { - True -> a - False -> b + /// Compares two floats, returning the smaller of the two. + /// + /// ## Examples + /// + /// > min(2.0, 2.3) + /// 2.0 + /// + pub fn min(a: Float, b: Float) -> Float { + case a <. b { + True -> a + False -> b + } } -} -/// Compares two floats, returning the larger of the two. -/// -/// ## Examples -/// -/// > max(2.0, 2.3) -/// 2.3 -/// -pub fn max(a: Float, b: Float) -> Float { - case a >. b { - True -> a - False -> b + /// Compares two floats, returning the larger of the two. + /// + /// ## Examples + /// + /// > max(2.0, 2.3) + /// 2.3 + /// + pub fn max(a: Float, b: Float) -> Float { + case a >. b { + True -> a + False -> b + } } -} -/// Rounds the value to the next highest whole number as a float. -/// -/// ## Examples -/// -/// > ceiling(2.3) -/// 3.0 -/// -pub external fn ceiling(Float) -> Float = - "math" "ceil" - -/// Rounds the value to the next lowest whole number as a float. -/// -/// ## Examples -/// -/// > floor(2.3) -/// 2.0 -/// -pub external fn floor(Float) -> Float = - "math" "floor" - -/// Rounds the value to the nearest whole number as an int. -/// -/// ## Examples -/// -/// > round(2.3) -/// 2 -/// -/// > round(2.5) -/// 3 -/// -pub external fn round(Float) -> Int = - "erlang" "round" - -/// Returns the value as an int, truncating all decimal digits. -/// -/// ## Examples -/// -/// > truncate(2.4343434847383438) -/// 2 -/// -pub external fn truncate(Float) -> Int = - "erlang" "trunc" - -/// Returns the absolute value of the input as a float. -/// -/// ## Examples -/// -/// > absolute_value(-12.5) -/// 12.5 -/// -/// > absolute_value(10.2) -/// 10.2 -/// -pub external fn absolute_value(Float) -> Float = - "erlang" "abs" - -/// Returns the results of the base being raised to the power of the -/// exponent, as a float. -/// -/// ## Examples -/// -/// > power(2.0, 2.0) -/// 4.0 -/// -/// > power(8.0, 1.5) -/// 64.0 -/// -pub external fn power(base: Float, exponent: Float) -> Float = - "math" "pow" - -/// Returns the square root of the input as a float. -/// -/// ## Examples -/// -/// > square_root(4.0) -/// Ok(2.0) -/// -/// > square_root(-16.0) -/// Error(Nil) -/// -pub fn square_root(number: Float) -> Result(Float, Nil) { - case number <. 0.0 { - True -> Error(Nil) - False -> Ok(power(number, 0.5)) + /// Rounds the value to the next highest whole number as a float. + /// + /// ## Examples + /// + /// > ceiling(2.3) + /// 3.0 + /// + pub external fn ceiling(Float) -> Float = + "math" "ceil" + + /// Rounds the value to the next lowest whole number as a float. + /// + /// ## Examples + /// + /// > floor(2.3) + /// 2.0 + /// + pub external fn floor(Float) -> Float = + "math" "floor" + + /// Rounds the value to the nearest whole number as an int. + /// + /// ## Examples + /// + /// > round(2.3) + /// 2 + /// + /// > round(2.5) + /// 3 + /// + pub external fn round(Float) -> Int = + "erlang" "round" + + /// Returns the value as an int, truncating all decimal digits. + /// + /// ## Examples + /// + /// > truncate(2.4343434847383438) + /// 2 + /// + pub external fn truncate(Float) -> Int = + "erlang" "trunc" + + /// Returns the absolute value of the input as a float. + /// + /// ## Examples + /// + /// > absolute_value(-12.5) + /// 12.5 + /// + /// > absolute_value(10.2) + /// 10.2 + /// + pub external fn absolute_value(Float) -> Float = + "erlang" "abs" + + /// Returns the results of the base being raised to the power of the + /// exponent, as a float. + /// + /// ## Examples + /// + /// > power(2.0, 2.0) + /// 4.0 + /// + /// > power(8.0, 1.5) + /// 64.0 + /// + pub external fn power(base: Float, exponent: Float) -> Float = + "math" "pow" + + /// Returns the square root of the input as a float. + /// + /// ## Examples + /// + /// > square_root(4.0) + /// Ok(2.0) + /// + /// > square_root(-16.0) + /// Error(Nil) + /// + pub fn square_root(number: Float) -> Result(Float, Nil) { + case number <. 0.0 { + True -> Error(Nil) + False -> Ok(power(number, 0.5)) + } } -} -/// Returns the negative of the value provided -/// -/// ## Examples -/// -/// > negate(1.) -/// -1. -/// -pub fn negate(x: Float) -> Float { - -1. *. x -} + /// Returns the negative of the value provided + /// + /// ## Examples + /// + /// > negate(1.) + /// -1. + /// + pub fn negate(x: Float) -> Float { + -1. *. x + } -/// Sums a list of Floats. -/// -/// ## Example -/// -/// > sum([1.0, 2.2, 3.3]) -/// 6.5 -/// -pub fn sum(numbers: List(Float)) -> Float { - numbers - |> do_sum(0.0) -} + /// Sums a list of Floats. + /// + /// ## Example + /// + /// > sum([1.0, 2.2, 3.3]) + /// 6.5 + /// + pub fn sum(numbers: List(Float)) -> Float { + numbers + |> do_sum(0.0) + } -fn do_sum(numbers: List(Float), initial: Float) -> Float { - case numbers { - [] -> initial - [x, ..rest] -> do_sum(rest, x +. initial) + fn do_sum(numbers: List(Float), initial: Float) -> Float { + case numbers { + [] -> initial + [x, ..rest] -> do_sum(rest, x +. initial) + } } -} -/// Multiplies a list of Floats and returns the product. -/// -/// ## Example -/// -/// > product([2.5, 3.2, 4.2]) -/// 33.6 -/// -pub fn product(numbers: List(Float)) -> Float { - case numbers { - [] -> 0. - _ -> do_product(numbers, 1.) + /// Multiplies a list of Floats and returns the product. + /// + /// ## Example + /// + /// > product([2.5, 3.2, 4.2]) + /// 33.6 + /// + pub fn product(numbers: List(Float)) -> Float { + case numbers { + [] -> 0. + _ -> do_product(numbers, 1.) + } } -} -fn do_product(numbers: List(Float), initial: Float) -> Float { - case numbers { - [] -> initial - [x, ..rest] -> do_product(rest, x *. initial) + fn do_product(numbers: List(Float), initial: Float) -> Float { + case numbers { + [] -> initial + [x, ..rest] -> do_product(rest, x *. initial) + } } } diff --git a/src/gleam/function.gleam b/src/gleam/function.gleam index 08e7efd..937a9bc 100644 --- a/src/gleam/function.gleam +++ b/src/gleam/function.gleam @@ -1,71 +1,73 @@ -import gleam/dynamic.{Dynamic} +if erlang { + import gleam/dynamic.{Dynamic} -/// Takes two functions and chains them together to form one function that takes -/// the input from the first and returns the output of the second. -/// -pub fn compose(fun1: fn(a) -> b, fun2: fn(b) -> c) -> fn(a) -> c { - fn(a) { fun2(fun1(a)) } -} + /// Takes two functions and chains them together to form one function that takes + /// the input from the first and returns the output of the second. + /// + pub fn compose(fun1: fn(a) -> b, fun2: fn(b) -> c) -> fn(a) -> c { + fn(a) { fun2(fun1(a)) } + } -/// Takes a function with arity two -/// and returns a curried equivalent. -/// fn(a, b) -> c becomes fn(a) -> fn(b) -> c -pub fn curry2(fun: fn(a, b) -> value) { - fn(a) { fn(b) { fun(a, b) } } -} + /// Takes a function with arity two + /// and returns a curried equivalent. + /// fn(a, b) -> c becomes fn(a) -> fn(b) -> c + pub fn curry2(fun: fn(a, b) -> value) { + fn(a) { fn(b) { fun(a, b) } } + } -/// Takes a function with arity three -/// and returns a curried equivalent. -/// fn(a, b, c) -> d becomes fn(a) -> fn(b) -> fn(c) -> d -pub fn curry3(fun: fn(a, b, c) -> value) { - fn(a) { fn(b) { fn(c) { fun(a, b, c) } } } -} + /// Takes a function with arity three + /// and returns a curried equivalent. + /// fn(a, b, c) -> d becomes fn(a) -> fn(b) -> fn(c) -> d + pub fn curry3(fun: fn(a, b, c) -> value) { + fn(a) { fn(b) { fn(c) { fun(a, b, c) } } } + } -/// Takes a function with arity four -/// and returns a curried equivalent. -pub fn curry4(fun: fn(a, b, c, d) -> value) { - fn(a) { fn(b) { fn(c) { fn(d) { fun(a, b, c, d) } } } } -} + /// Takes a function with arity four + /// and returns a curried equivalent. + pub fn curry4(fun: fn(a, b, c, d) -> value) { + fn(a) { fn(b) { fn(c) { fn(d) { fun(a, b, c, d) } } } } + } -/// Takes a function with arity five -/// and returns a curried equivalent. -pub fn curry5(fun: fn(a, b, c, d, e) -> value) { - fn(a) { fn(b) { fn(c) { fn(d) { fn(e) { fun(a, b, c, d, e) } } } } } -} + /// Takes a function with arity five + /// and returns a curried equivalent. + pub fn curry5(fun: fn(a, b, c, d, e) -> value) { + fn(a) { fn(b) { fn(c) { fn(d) { fn(e) { fun(a, b, c, d, e) } } } } } + } -/// Takes a function with arity six -/// and returns a curried equivalent. -pub fn curry6(fun: fn(a, b, c, d, e, f) -> value) { - fn(a) { - fn(b) { fn(c) { fn(d) { fn(e) { fn(f) { fun(a, b, c, d, e, f) } } } } } + /// Takes a function with arity six + /// and returns a curried equivalent. + pub fn curry6(fun: fn(a, b, c, d, e, f) -> value) { + fn(a) { + fn(b) { fn(c) { fn(d) { fn(e) { fn(f) { fun(a, b, c, d, e, f) } } } } } + } } -} -/// Takes a function that takes two arguments and returns a new function that -/// takes the same two arguments, but in reverse order. -/// -pub fn flip(fun: fn(a, b) -> c) -> fn(b, a) -> c { - fn(b, a) { fun(a, b) } -} + /// Takes a function that takes two arguments and returns a new function that + /// takes the same two arguments, but in reverse order. + /// + pub fn flip(fun: fn(a, b) -> c) -> fn(b, a) -> c { + fn(b, a) { fun(a, b) } + } -/// A function that always returns its input value. -/// -pub fn identity(x: a) -> a { - x -} + /// A function that always returns its input value. + /// + pub fn identity(x: a) -> a { + x + } -pub type Exception { - Exited(Dynamic) - Thrown(Dynamic) - Errored(Dynamic) -} + pub type Exception { + Exited(Dynamic) + Thrown(Dynamic) + Errored(Dynamic) + } -/// Gleam doesn't offer any way to raise exceptions, but they may still occur -/// due to bugs when working with unsafe code, such as when calling Erlang -/// function. -/// -/// This function will catch any error thrown and convert it into a result -/// rather than crashing the process. -/// -pub external fn rescue(fn() -> a) -> Result(a, Exception) = - "gleam_stdlib" "rescue" + /// Gleam doesn't offer any way to raise exceptions, but they may still occur + /// due to bugs when working with unsafe code, such as when calling Erlang + /// function. + /// + /// This function will catch any error thrown and convert it into a result + /// rather than crashing the process. + /// + pub external fn rescue(fn() -> a) -> Result(a, Exception) = + "gleam_stdlib" "rescue" +} diff --git a/src/gleam/int.gleam b/src/gleam/int.gleam index b77500c..b65d3a3 100644 --- a/src/gleam/int.gleam +++ b/src/gleam/int.gleam @@ -1,222 +1,224 @@ -import gleam/order.{Order} - -pub type Int = - Int - -/// Returns the absolute value of the input. -/// -/// ## Examples -/// -/// > absolute_value(-12) -/// 12 -/// -/// > absolute_value(10) -/// 10 -/// -pub fn absolute_value(num: Int) -> Int { - case num >= 0 { - True -> num - False -> num * -1 +if erlang { + import gleam/order.{Order} + + pub type Int = + Int + + /// Returns the absolute value of the input. + /// + /// ## Examples + /// + /// > absolute_value(-12) + /// 12 + /// + /// > absolute_value(10) + /// 10 + /// + pub fn absolute_value(num: Int) -> Int { + case num >= 0 { + True -> num + False -> num * -1 + } } -} -/// Parses a given string as an int if possible. -/// -/// ## Examples -/// -/// > parse("2") -/// Ok(2) -/// -/// > parse("ABC") -/// Error(Nil) -/// -pub external fn parse(String) -> Result(Int, Nil) = - "gleam_stdlib" "parse_int" - -/// Prints a given int to a string. -/// -/// ## Examples -/// -/// > to_string(2) -/// "2" -/// -pub external fn to_string(Int) -> String = - "erlang" "integer_to_binary" - -/// Prints a given int to a string using the base number provided. -/// -/// ## Examples -/// -/// > to_base_string(2, 2) -/// "10" -/// -/// > to_base_string(48, 16) -/// "30" -/// -/// > to_base_string(48, 36) -/// "1C" -/// -pub external fn to_base_string(Int, Int) -> String = - "erlang" "integer_to_binary" - -/// Takes an int and returns its value as a float -/// -/// ## Examples -/// -/// > to_float(5) -/// 5. -/// -/// > to_float(0) -/// 0. -/// -/// > to_float(-3) -/// -3. -/// -pub external fn to_float(a: Int) -> Float = - "erlang" "float" - -/// Restricts an Int between a lower and upper bound -/// -/// ## Examples -/// -/// ``` -/// > clamp(40, min: 50, max: 60) -/// 50 -/// ``` -/// -pub fn clamp(n: Int, min min_bound: Int, max max_bound: Int) -> Int { - n - |> min(max_bound) - |> max(min_bound) -} + /// Parses a given string as an int if possible. + /// + /// ## Examples + /// + /// > parse("2") + /// Ok(2) + /// + /// > parse("ABC") + /// Error(Nil) + /// + pub external fn parse(String) -> Result(Int, Nil) = + "gleam_stdlib" "parse_int" + + /// Prints a given int to a string. + /// + /// ## Examples + /// + /// > to_string(2) + /// "2" + /// + pub external fn to_string(Int) -> String = + "erlang" "integer_to_binary" + + /// Prints a given int to a string using the base number provided. + /// + /// ## Examples + /// + /// > to_base_string(2, 2) + /// "10" + /// + /// > to_base_string(48, 16) + /// "30" + /// + /// > to_base_string(48, 36) + /// "1C" + /// + pub external fn to_base_string(Int, Int) -> String = + "erlang" "integer_to_binary" + + /// Takes an int and returns its value as a float + /// + /// ## Examples + /// + /// > to_float(5) + /// 5. + /// + /// > to_float(0) + /// 0. + /// + /// > to_float(-3) + /// -3. + /// + pub external fn to_float(a: Int) -> Float = + "erlang" "float" + + /// Restricts an Int between a lower and upper bound + /// + /// ## Examples + /// + /// ``` + /// > clamp(40, min: 50, max: 60) + /// 50 + /// ``` + /// + pub fn clamp(n: Int, min min_bound: Int, max max_bound: Int) -> Int { + n + |> min(max_bound) + |> max(min_bound) + } -/// Compares two ints, returning an order. -/// -/// ## Examples -/// -/// > compare(2, 3) -/// Lt -/// -/// > compare(4, 3) -/// Gt -/// -/// > compare(3, 3) -/// Eq -/// -pub fn compare(a: Int, with b: Int) -> Order { - case a == b { - True -> order.Eq - False -> - case a < b { - True -> order.Lt - False -> order.Gt - } + /// Compares two ints, returning an order. + /// + /// ## Examples + /// + /// > compare(2, 3) + /// Lt + /// + /// > compare(4, 3) + /// Gt + /// + /// > compare(3, 3) + /// Eq + /// + pub fn compare(a: Int, with b: Int) -> Order { + case a == b { + True -> order.Eq + False -> + case a < b { + True -> order.Lt + False -> order.Gt + } + } } -} -/// Compares two int, returning the smaller of the two. -/// -/// ## Examples -/// -/// > min(2, 3) -/// 2 -/// -pub fn min(a: Int, b: Int) -> Int { - case a < b { - True -> a - False -> b + /// Compares two int, returning the smaller of the two. + /// + /// ## Examples + /// + /// > min(2, 3) + /// 2 + /// + pub fn min(a: Int, b: Int) -> Int { + case a < b { + True -> a + False -> b + } } -} -/// Compares two int, returning the larger of the two. -/// -/// ## Examples -/// -/// > max(2, 3) -/// 3 -/// -pub fn max(a: Int, b: Int) -> Int { - case a > b { - True -> a - False -> b + /// Compares two int, returning the larger of the two. + /// + /// ## Examples + /// + /// > max(2, 3) + /// 3 + /// + pub fn max(a: Int, b: Int) -> Int { + case a > b { + True -> a + False -> b + } } -} -/// Returns whether the value provided is even. -/// -/// ## Examples -/// -/// > is_even(2) -/// True -/// -/// > is_even(3) -/// False -/// -pub fn is_even(x: Int) -> Bool { - x % 2 == 0 -} + /// Returns whether the value provided is even. + /// + /// ## Examples + /// + /// > is_even(2) + /// True + /// + /// > is_even(3) + /// False + /// + pub fn is_even(x: Int) -> Bool { + x % 2 == 0 + } -/// Returns whether the value provided is odd. -/// -/// ## Examples -/// -/// > is_odd(3) -/// True -/// -/// > is_odd(2) -/// False -/// -pub fn is_odd(x: Int) -> Bool { - x % 2 != 0 -} + /// Returns whether the value provided is odd. + /// + /// ## Examples + /// + /// > is_odd(3) + /// True + /// + /// > is_odd(2) + /// False + /// + pub fn is_odd(x: Int) -> Bool { + x % 2 != 0 + } -/// Returns the negative of the value provided -/// -/// ## Examples -/// -/// > negate(1) -/// -1 -/// -pub fn negate(x: Int) -> Int { - -1 * x -} + /// Returns the negative of the value provided + /// + /// ## Examples + /// + /// > negate(1) + /// -1 + /// + pub fn negate(x: Int) -> Int { + -1 * x + } -/// Sums a list of Ints. -/// -/// ## Example -/// -/// > sum([1, 2, 3]) -/// 6 -/// -pub fn sum(numbers: List(Int)) -> Int { - numbers - |> do_sum(0) -} + /// Sums a list of Ints. + /// + /// ## Example + /// + /// > sum([1, 2, 3]) + /// 6 + /// + pub fn sum(numbers: List(Int)) -> Int { + numbers + |> do_sum(0) + } -fn do_sum(numbers: List(Int), initial: Int) -> Int { - case numbers { - [] -> initial - [x, ..rest] -> do_sum(rest, x + initial) + fn do_sum(numbers: List(Int), initial: Int) -> Int { + case numbers { + [] -> initial + [x, ..rest] -> do_sum(rest, x + initial) + } } -} -/// Multiplies a list of Ints and returns the product. -/// -/// ## Example -/// -/// > product([2, 3, 4]) -/// 24 -/// -pub fn product(numbers: List(Int)) -> Int { - case numbers { - [] -> 0 - _ -> do_product(numbers, 1) + /// Multiplies a list of Ints and returns the product. + /// + /// ## Example + /// + /// > product([2, 3, 4]) + /// 24 + /// + pub fn product(numbers: List(Int)) -> Int { + case numbers { + [] -> 0 + _ -> do_product(numbers, 1) + } } -} -fn do_product(numbers: List(Int), initial: Int) -> Int { - case numbers { - [] -> initial - [x, ..rest] -> do_product(rest, x * initial) + fn do_product(numbers: List(Int), initial: Int) -> Int { + case numbers { + [] -> initial + [x, ..rest] -> do_product(rest, x * initial) + } } } diff --git a/src/gleam/io.gleam b/src/gleam/io.gleam index a5b37df..f6e7f40 100644 --- a/src/gleam/io.gleam +++ b/src/gleam/io.gleam @@ -1,75 +1,77 @@ -external type DoNotLeak +if erlang { + external type DoNotLeak -external fn erl_print(String, List(a)) -> DoNotLeak = - "io" "fwrite" + external fn erl_print(String, List(a)) -> DoNotLeak = + "io" "fwrite" -/// Writes a string to standard output. -/// -/// ## Example -/// -/// > io.print("Hi mum") -/// // -> Hi mum -/// Nil -/// -pub fn print(string: String) -> Nil { - erl_print(string, []) - Nil -} + /// Writes a string to standard output. + /// + /// ## Example + /// + /// > io.print("Hi mum") + /// // -> Hi mum + /// Nil + /// + pub fn print(string: String) -> Nil { + erl_print(string, []) + Nil + } -/// Writes a string to standard output, appending a newline to the end. -/// -/// ## Example -/// -/// > io.println("Hi mum") -/// // -> Hi mum -/// Nil -/// -pub fn println(string: String) -> Nil { - erl_print("~ts\n", [string]) - Nil -} + /// Writes a string to standard output, appending a newline to the end. + /// + /// ## Example + /// + /// > io.println("Hi mum") + /// // -> Hi mum + /// Nil + /// + pub fn println(string: String) -> Nil { + erl_print("~ts\n", [string]) + Nil + } -/// Prints a value to standard output using Erlang syntax. -/// -/// The value is returned after being printed so it can be used in pipelines. -/// -/// ## Example -/// -/// > io.debug("Hi mum") -/// // -> <<"Hi mum">> -/// "Hi mum" -/// -/// > io.debug(Ok(1)) -/// // -> {ok, 1} -/// Ok(1) -/// -/// > import list -/// > [1, 2] -/// > |> list.map(fn(x) { x + 1 }) -/// > |> io.debug -/// > |> list.map(fn(x) { x * 2 }) -/// // -> [2, 3] -/// [4, 6] -/// -pub fn debug(term: anything) -> anything { - erl_print("~tp\n", [term]) - term -} + /// Prints a value to standard output using Erlang syntax. + /// + /// The value is returned after being printed so it can be used in pipelines. + /// + /// ## Example + /// + /// > io.debug("Hi mum") + /// // -> <<"Hi mum">> + /// "Hi mum" + /// + /// > io.debug(Ok(1)) + /// // -> {ok, 1} + /// Ok(1) + /// + /// > import list + /// > [1, 2] + /// > |> list.map(fn(x) { x + 1 }) + /// > |> io.debug + /// > |> list.map(fn(x) { x * 2 }) + /// // -> [2, 3] + /// [4, 6] + /// + pub fn debug(term: anything) -> anything { + erl_print("~tp\n", [term]) + term + } -/// Error value returned by `get_line` function -/// -pub type GetLineError { - Eof - NoData -} + /// Error value returned by `get_line` function + /// + pub type GetLineError { + Eof + NoData + } -/// Reads a line from standard input with the given prompt. -/// -/// # Example -/// -/// > io.get_line("Language: ") -/// // -> Language: <- gleam -/// Ok("gleam\n") -/// -pub external fn get_line(prompt: String) -> Result(String, GetLineError) = - "gleam_stdlib" "get_line" + /// Reads a line from standard input with the given prompt. + /// + /// # Example + /// + /// > io.get_line("Language: ") + /// // -> Language: <- gleam + /// Ok("gleam\n") + /// + pub external fn get_line(prompt: String) -> Result(String, GetLineError) = + "gleam_stdlib" "get_line" +} diff --git a/src/gleam/iterator.gleam b/src/gleam/iterator.gleam index 1b347c4..ff2e020 100644 --- a/src/gleam/iterator.gleam +++ b/src/gleam/iterator.gleam @@ -1,1029 +1,1037 @@ -import gleam/list -import gleam/map.{Map} - -// Internal private representation of an Iterator -type Action(element) { - Stop - Continue(element, fn() -> Action(element)) -} +if erlang { + import gleam/list + import gleam/map.{Map} + + // 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) -} - -// 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 }) -} + /// 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) + } -/// 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 -} + // 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 }) + } -/// 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)) + /// 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 } -} -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)) - } + /// 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)) } } -} - -/// 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) -> + fn do_take(continuation: fn() -> Action(e), desired: Int) -> fn() -> Action(e) { + fn() { case desired > 0 { - True -> do_drop(next, desired - 1) - False -> Continue(e, next) + False -> Stop + True -> + case continuation() { + Stop -> Stop + Continue(e, next) -> Continue(e, do_take(next, desired - 1)) + } } + } } -} -/// 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 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_map(continuation: fn() -> Action(a), f: fn(a) -> b) -> fn() -> Action(b) { - fn() { + fn do_drop(continuation: fn() -> Action(e), desired: Int) -> Action(e) { case continuation() { Stop -> Stop - Continue(e, continuation) -> Continue(f(e), do_map(continuation, f)) + Continue(e, next) -> + case desired > 0 { + True -> do_drop(next, desired - 1) + False -> Continue(e, next) + } } } -} -/// 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_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() + /// 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 } -} -/// 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_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) }) + 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)) + } + } } -} -/// 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 -} - -/// 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 -} - -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. + /// + /// 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 } -} -/// 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_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 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 -} + /// 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 + } -/// 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) + 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) }) } } - unfold(start, next_step) -} - -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) - } + /// 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 } -} -/// 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 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 + } -fn do_index( - continuation: fn() -> Action(element), - next: Int, -) -> fn() -> Action(#(Int, element)) { - fn() { + fn do_filter( + continuation: fn() -> Action(e), + predicate: fn(e) -> Bool, + ) -> Action(e) { case continuation() { Stop -> Stop - Continue(e, continuation) -> - Continue(#(next, e), do_index(continuation, next + 1)) + Continue(e, iterator) -> + case predicate(e) { + True -> Continue(e, fn() { do_filter(iterator, predicate) }) + False -> do_filter(iterator, 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 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 + } -/// 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 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) + } + } + + unfold(start, next_step) + } -fn do_take_while( - continuation: fn() -> Action(element), - predicate: fn(element) -> Bool, -) -> fn() -> Action(element) { - fn() { + fn do_find( + continuation: fn() -> Action(a), + f: fn(a) -> Bool, + ) -> Result(a, Nil) { case continuation() { - Stop -> Stop + Stop -> Error(Nil) Continue(e, next) -> - case predicate(e) { - False -> Stop - True -> Continue(e, do_take_while(next, predicate)) + case f(e) { + True -> Ok(e) + False -> do_find(next, f) } } } -} -/// 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 -} + /// 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) + } -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) + 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)) } + } } -} -/// 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 -} + /// 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 + } -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 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_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)) + } } } } -} -/// 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 -} + /// 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_zip( - left: fn() -> Action(a), - right: fn() -> Action(b), -) -> fn() -> Action(#(a, b)) { - fn() { - case left() { + fn do_drop_while( + continuation: fn() -> Action(element), + predicate: fn(element) -> Bool, + ) -> Action(element) { + case continuation() { 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)) + Continue(e, next) -> + case predicate(e) { + False -> Continue(e, next) + True -> do_drop_while(next, predicate) } } } -} -/// 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 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 + } -// 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_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 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 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) + 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 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) }) + /// 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 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) + // Result of collecting a single chunk by key + type Chunk(element, key) { + AnotherBy(List(element), key, element, fn() -> Action(element)) + LastBy(List(element)) + } + + 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) + } + } } } - |> Iterator -} -// Result of collecting a single sized chunk -type SizedChunk(element) { - Another(List(element), fn() -> Action(element)) - Last(List(element)) - None -} + 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_sized_chunk( - continuation: fn() -> Action(element), - left: Int, - current_chunk: List(element), -) -> SizedChunk(element) { - case continuation() { - Stop -> - case current_chunk { - [] -> None - remaining -> Last(list.reverse(remaining)) + /// 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) } - 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) + } + |> Iterator + } + + // Result of collecting a single sized chunk + type SizedChunk(element) { + Another(List(element), fn() -> Action(element)) + Last(List(element)) + None + } + + fn next_sized_chunk( + continuation: fn() -> Action(element), + left: Int, + current_chunk: List(element), + ) -> SizedChunk(element) { + case continuation() { + Stop -> + case current_chunk { + [] -> None + 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) + } } } } -} -fn do_sized_chunk( - continuation: fn() -> Action(element), - count: Int, -) -> fn() -> Action(List(element)) { - fn() { - case next_sized_chunk(continuation, count, []) { - None -> 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, []) { + None -> 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) + } -fn update_group_with( - el: element, -) -> fn(Result(List(element), Nil)) -> List(element) { - fn(maybe_group) { - case maybe_group { - Ok(group) -> [el, ..group] - Error(Nil) -> [el] + fn update_group_with( + el: element, + ) -> fn(Result(List(element), Nil)) -> List(element) { + fn(maybe_group) { + case maybe_group { + Ok(group) -> [el, ..group] + Error(Nil) -> [el] + } } } -} -fn group_updater( - f: fn(element) -> key, -) -> fn(element, Map(key, List(element))) -> Map(key, List(element)) { - fn(elem, groups) { - groups - |> map.update(f(elem), update_group_with(elem)) + fn group_updater( + f: fn(element) -> key, + ) -> fn(element, Map(key, List(element))) -> Map(key, List(element)) { + fn(elem, groups) { + groups + |> map.update(f(elem), update_group_with(elem)) + } } -} -/// Returns a `Map(k, List(element))` of elements from the given iterator -/// grouped with the given key function. -/// -/// The order within each group is preserved from the iterator. -/// -/// ## Examples -/// -/// > from_list([1, 2, 3, 4, 5, 6]) |> group(by: fn(n) { n % 3 }) -/// map.from_list([#(0, [3, 6]), #(1, [1, 4]), #(2, [2, 5])]) -/// -pub fn group( - in iterator: Iterator(element), - by key: fn(element) -> key, -) -> Map(key, List(element)) { - iterator - |> fold(map.new(), group_updater(key)) - |> map.map_values(fn(_, group) { list.reverse(group) }) -} + /// Returns a `Map(k, List(element))` of elements from the given iterator + /// grouped with the given key function. + /// + /// The order within each group is preserved from the iterator. + /// + /// ## Examples + /// + /// > from_list([1, 2, 3, 4, 5, 6]) |> group(by: fn(n) { n % 3 }) + /// map.from_list([#(0, [3, 6]), #(1, [1, 4]), #(2, [2, 5])]) + /// + pub fn group( + in iterator: Iterator(element), + by key: fn(element) -> key, + ) -> Map(key, List(element)) { + iterator + |> fold(map.new(), group_updater(key)) + |> map.map_values(fn(_, group) { list.reverse(group) }) + } -/// This function acts similar to fold, but does not take an initial state. -/// Instead, it starts from the first yielded element -/// and combines it with each subsequent element in turn using the given function. -/// The function is called as f(current_element, accumulator). -/// -/// Returns `Ok` to indicate a successful run, and `Error` if called on an empty iterator. -/// -/// ## Examples -/// -/// > from_list([]) |> reduce(fn(x, y) { x + y }) -/// Error(Nil) -/// -/// > from_list([1, 2, 3, 4, 5]) |> reduce(fn(x, y) { x + y }) -/// Ok(15) -/// -pub fn reduce( - over iterator: Iterator(e), - with f: fn(e, e) -> e, -) -> Result(e, Nil) { - case iterator.continuation() { - Stop -> Error(Nil) - Continue(e, next) -> - do_fold(next, f, e) - |> Ok + /// This function acts similar to fold, but does not take an initial state. + /// Instead, it starts from the first yielded element + /// and combines it with each subsequent element in turn using the given function. + /// The function is called as f(current_element, accumulator). + /// + /// Returns `Ok` to indicate a successful run, and `Error` if called on an empty iterator. + /// + /// ## Examples + /// + /// > from_list([]) |> reduce(fn(x, y) { x + y }) + /// Error(Nil) + /// + /// > from_list([1, 2, 3, 4, 5]) |> reduce(fn(x, y) { x + y }) + /// Ok(15) + /// + pub fn reduce( + over iterator: Iterator(e), + with f: fn(e, e) -> e, + ) -> Result(e, Nil) { + case iterator.continuation() { + Stop -> Error(Nil) + Continue(e, next) -> + do_fold(next, f, e) + |> Ok + } } -} -/// Returns the last element in the given iterator. -/// -/// Returns `Error(Nil)` if the iterator is empty. -/// -/// This function runs in linear time. -/// -/// ## Examples -/// -/// > empty() |> last -/// Error(Nil) -/// -/// > range(1, 10) |> last -/// Ok(9) -/// -pub fn last(iterator: Iterator(element)) -> Result(element, Nil) { - iterator - |> reduce(fn(elem, _) { elem }) -} + /// Returns the last element in the given iterator. + /// + /// Returns `Error(Nil)` if the iterator is empty. + /// + /// This function runs in linear time. + /// + /// ## Examples + /// + /// > empty() |> last + /// Error(Nil) + /// + /// > range(1, 10) |> last + /// Ok(9) + /// + pub fn last(iterator: Iterator(element)) -> Result(element, Nil) { + iterator + |> reduce(fn(elem, _) { elem }) + } -/// Creates an iterator that yields no elements. -/// -/// ## Examples -/// -/// > empty() |> to_list -/// [] -/// -pub fn empty() -> Iterator(element) { - Iterator(stop) -} + /// Creates an iterator that yields no elements. + /// + /// ## Examples + /// + /// > empty() |> to_list + /// [] + /// + pub fn empty() -> Iterator(element) { + Iterator(stop) + } -/// Creates an iterator that yields exactly one element provided by calling the given function. -/// -/// ## Examples -/// -/// > once(fn() { 1 }) |> to_list -/// [1] -/// -pub fn once(f: fn() -> element) -> Iterator(element) { - fn() { Continue(f(), stop) } - |> Iterator -} + /// Creates an iterator that yields exactly one element provided by calling the given function. + /// + /// ## Examples + /// + /// > once(fn() { 1 }) |> to_list + /// [1] + /// + pub fn once(f: fn() -> element) -> Iterator(element) { + fn() { Continue(f(), stop) } + |> Iterator + } -/// Creates an iterator that yields the given element exactly once. -/// -/// ## Examples -/// -/// > single(1) |> to_list -/// [1] -/// -pub fn single(elem: element) -> Iterator(element) { - once(fn() { elem }) -} + /// Creates an iterator that yields the given element exactly once. + /// + /// ## Examples + /// + /// > single(1) |> to_list + /// [1] + /// + pub fn single(elem: element) -> Iterator(element) { + once(fn() { elem }) + } -fn do_interleave( - current: fn() -> Action(element), - next: fn() -> Action(element), -) -> Action(element) { - case current() { - Stop -> next() - Continue(e, next_other) -> - Continue(e, fn() { do_interleave(next, next_other) }) + fn do_interleave( + current: fn() -> Action(element), + next: fn() -> Action(element), + ) -> Action(element) { + case current() { + Stop -> next() + Continue(e, next_other) -> + Continue(e, fn() { do_interleave(next, next_other) }) + } } -} -/// Creates an iterator that alternates between the two given iterators -/// until both have run out. -/// -/// ## Examples -/// -/// > from_list([1, 2, 3, 4]) |> interleave(from_list([11, 12, 13, 14])) |> to_list -/// [1, 11, 2, 12, 3, 13, 4, 14] -/// -/// > from_list([1, 2, 3, 4]) |> interleave(from_list([100])) |> to_list -/// [1, 100, 2, 3, 4] -/// -pub fn interleave( - left: Iterator(element), - with right: Iterator(element), -) -> Iterator(element) { - fn() { do_interleave(left.continuation, right.continuation) } - |> Iterator + /// Creates an iterator that alternates between the two given iterators + /// until both have run out. + /// + /// ## Examples + /// + /// > from_list([1, 2, 3, 4]) |> interleave(from_list([11, 12, 13, 14])) |> to_list + /// [1, 11, 2, 12, 3, 13, 4, 14] + /// + /// > from_list([1, 2, 3, 4]) |> interleave(from_list([100])) |> to_list + /// [1, 100, 2, 3, 4] + /// + pub fn interleave( + left: Iterator(element), + with right: Iterator(element), + ) -> Iterator(element) { + fn() { do_interleave(left.continuation, right.continuation) } + |> Iterator + } } diff --git a/src/gleam/list.gleam b/src/gleam/list.gleam index 2a91733..33e32d1 100644 --- a/src/gleam/list.gleam +++ b/src/gleam/list.gleam @@ -18,1556 +18,1560 @@ //// } //// -import gleam/int -import gleam/pair -import gleam/order.{Order} - -pub type List(elements) = - List(elements) - -/// An error value returned by the `strict_zip` function. -/// -pub type LengthMismatch { - LengthMismatch -} +if erlang { + import gleam/int + import gleam/pair + import gleam/order.{Order} + + pub type List(elements) = + List(elements) + + /// An error value returned by the `strict_zip` function. + /// + pub type LengthMismatch { + LengthMismatch + } -/// Counts the number of elements in a given list. -/// -/// This function has to traverse the list to determine the number of elements, -/// so it runs in linear time. -/// -/// This function is natively implemented by the virtual machine and is highly -/// optimised. -/// -/// ## Examples -/// -/// > length([]) -/// 0 -/// -/// > length([1]) -/// 1 -/// -/// > length([1, 2]) -/// 2 -/// -pub external fn length(of: List(a)) -> Int = - "erlang" "length" - -/// Creates a new list from a given list containing the same elements but in the -/// opposite order. -/// -/// This function has to traverse the list to create the new reversed list, so -/// it runs in linear time. -/// -/// This function is natively implemented by the virtual machine and is highly -/// optimised. -/// -/// ## Examples -/// -/// > reverse([]) -/// [] -/// -/// > reverse([1]) -/// [1] -/// -/// > reverse([1, 2]) -/// [2, 1] -/// -pub external fn reverse(List(a)) -> List(a) = - "lists" "reverse" - -/// Determines whether or not the list is empty. -/// -/// This function runs in constant time. -/// -/// ## Examples -/// -/// > is_empty([]) -/// True -/// -/// > is_empty([1]) -/// False -/// -/// > is_empty([1, 1]) -/// False -/// -pub fn is_empty(list: List(a)) -> Bool { - list == [] -} + /// Counts the number of elements in a given list. + /// + /// This function has to traverse the list to determine the number of elements, + /// so it runs in linear time. + /// + /// This function is natively implemented by the virtual machine and is highly + /// optimised. + /// + /// ## Examples + /// + /// > length([]) + /// 0 + /// + /// > length([1]) + /// 1 + /// + /// > length([1, 2]) + /// 2 + /// + pub external fn length(of: List(a)) -> Int = + "erlang" "length" + + /// Creates a new list from a given list containing the same elements but in the + /// opposite order. + /// + /// This function has to traverse the list to create the new reversed list, so + /// it runs in linear time. + /// + /// This function is natively implemented by the virtual machine and is highly + /// optimised. + /// + /// ## Examples + /// + /// > reverse([]) + /// [] + /// + /// > reverse([1]) + /// [1] + /// + /// > reverse([1, 2]) + /// [2, 1] + /// + pub external fn reverse(List(a)) -> List(a) = + "lists" "reverse" + + /// Determines whether or not the list is empty. + /// + /// This function runs in constant time. + /// + /// ## Examples + /// + /// > is_empty([]) + /// True + /// + /// > is_empty([1]) + /// False + /// + /// > is_empty([1, 1]) + /// False + /// + pub fn is_empty(list: List(a)) -> Bool { + list == [] + } -/// Determines whether or not a given element exists within a given list. -/// -/// This function traverses the list to find the element, so it runs in linear -/// time. -/// -/// ## Examples -/// -/// > [] |> contains(any: 0) -/// True -/// -/// > [0] |> contains(any: 0) -/// True -/// -/// > [1] |> contains(any: 0) -/// False -/// -/// > [1, 1] |> contains(any: 0) -/// False -/// -/// > [1, 0] |> contains(any: 0) -/// True -/// -pub fn contains(list: List(a), any elem: a) -> Bool { - case list { - [] -> False - [head, ..rest] -> head == elem || contains(rest, elem) + /// Determines whether or not a given element exists within a given list. + /// + /// This function traverses the list to find the element, so it runs in linear + /// time. + /// + /// ## Examples + /// + /// > [] |> contains(any: 0) + /// True + /// + /// > [0] |> contains(any: 0) + /// True + /// + /// > [1] |> contains(any: 0) + /// False + /// + /// > [1, 1] |> contains(any: 0) + /// False + /// + /// > [1, 0] |> contains(any: 0) + /// True + /// + pub fn contains(list: List(a), any elem: a) -> Bool { + case list { + [] -> False + [head, ..rest] -> head == elem || contains(rest, elem) + } } -} -/// Gets the first element from the start of the list, if there is one. -/// -/// ## Examples -/// -/// > head([]) -/// Error(Nil) -/// -/// > head([0]) -/// Ok(0) -/// -/// > head([1, 2]) -/// Ok(1) -/// -pub fn head(list: List(a)) -> Result(a, Nil) { - case list { - [] -> Error(Nil) - [x, .._] -> Ok(x) + /// Gets the first element from the start of the list, if there is one. + /// + /// ## Examples + /// + /// > head([]) + /// Error(Nil) + /// + /// > head([0]) + /// Ok(0) + /// + /// > head([1, 2]) + /// Ok(1) + /// + pub fn head(list: List(a)) -> Result(a, Nil) { + case list { + [] -> Error(Nil) + [x, .._] -> Ok(x) + } } -} -/// Gets the list minus the first element. If the list is empty `Error(Nil)` is -/// returned. -/// -/// This function runs in constant time and does not make a copy of the list. -/// -/// ## Examples -/// -/// > tail([]) -/// Error(Nil) -/// -/// > tail([0]) -/// Ok([]) -/// -/// > tail([1, 2]) -/// Ok([2]) -/// -pub fn tail(list: List(a)) -> Result(List(a), Nil) { - case list { - [] -> Error(Nil) - [_, ..xs] -> Ok(xs) + /// Gets the list minus the first element. If the list is empty `Error(Nil)` is + /// returned. + /// + /// This function runs in constant time and does not make a copy of the list. + /// + /// ## Examples + /// + /// > tail([]) + /// Error(Nil) + /// + /// > tail([0]) + /// Ok([]) + /// + /// > tail([1, 2]) + /// Ok([2]) + /// + pub fn tail(list: List(a)) -> Result(List(a), Nil) { + case list { + [] -> Error(Nil) + [_, ..xs] -> Ok(xs) + } } -} -fn do_filter(list: List(a), fun: fn(a) -> Bool, acc: List(a)) -> List(a) { - case list { - [] -> reverse(acc) - [x, ..xs] -> { - let new_acc = case fun(x) { - True -> [x, ..acc] - False -> acc + fn do_filter(list: List(a), fun: fn(a) -> Bool, acc: List(a)) -> List(a) { + case list { + [] -> reverse(acc) + [x, ..xs] -> { + let new_acc = case fun(x) { + True -> [x, ..acc] + False -> acc + } + do_filter(xs, fun, new_acc) } - do_filter(xs, fun, new_acc) } } -} -/// Returns a new list containing only the elements from the first list for -/// which the given functions returns `True`. -/// -/// ## Examples -/// -/// > filter([2, 4, 6, 1], fn(x) { x > 2 }) -/// [4, 6] -/// -/// > filter([2, 4, 6, 1], fn(x) { x > 6 }) -/// [] -/// -pub fn filter(list: List(a), for predicate: fn(a) -> Bool) -> List(a) { - do_filter(list, predicate, []) -} + /// Returns a new list containing only the elements from the first list for + /// which the given functions returns `True`. + /// + /// ## Examples + /// + /// > filter([2, 4, 6, 1], fn(x) { x > 2 }) + /// [4, 6] + /// + /// > filter([2, 4, 6, 1], fn(x) { x > 6 }) + /// [] + /// + pub fn filter(list: List(a), for predicate: fn(a) -> Bool) -> List(a) { + do_filter(list, predicate, []) + } -fn do_filter_map( - list: List(a), - fun: fn(a) -> Result(b, e), - acc: List(b), -) -> List(b) { - case list { - [] -> reverse(acc) - [x, ..xs] -> { - let new_acc = case fun(x) { - Ok(x) -> [x, ..acc] - Error(_) -> acc + fn do_filter_map( + list: List(a), + fun: fn(a) -> Result(b, e), + acc: List(b), + ) -> List(b) { + case list { + [] -> reverse(acc) + [x, ..xs] -> { + let new_acc = case fun(x) { + Ok(x) -> [x, ..acc] + Error(_) -> acc + } + do_filter_map(xs, fun, new_acc) } - do_filter_map(xs, fun, new_acc) } } -} - -/// Returns a new list containing only the elements from the first list for -/// which the given functions returns `Ok(_)`. -/// -/// ## Examples -/// -/// > filter_map([2, 4, 6, 1], Error) -/// [] -/// -/// > filter_map([2, 4, 6, 1], fn(x) { Ok(x + 1) }) -/// [3, 4, 6, 2] -/// -pub fn filter_map(list: List(a), with fun: fn(a) -> Result(b, e)) -> List(b) { - do_filter_map(list, fun, []) -} -fn do_map(list: List(a), fun: fn(a) -> b, acc: List(b)) -> List(b) { - case list { - [] -> reverse(acc) - [x, ..xs] -> do_map(xs, fun, [fun(x), ..acc]) + /// Returns a new list containing only the elements from the first list for + /// which the given functions returns `Ok(_)`. + /// + /// ## Examples + /// + /// > filter_map([2, 4, 6, 1], Error) + /// [] + /// + /// > filter_map([2, 4, 6, 1], fn(x) { Ok(x + 1) }) + /// [3, 4, 6, 2] + /// + pub fn filter_map(list: List(a), with fun: fn(a) -> Result(b, e)) -> List(b) { + do_filter_map(list, fun, []) } -} -/// Returns a new list containing only the elements of the first list after the -/// function has been applied to each one. -/// -/// ## Examples -/// -/// > map([2, 4, 6], fn(x) { x * 2 }) -/// [4, 8, 12] -/// -pub fn map(list: List(a), with fun: fn(a) -> b) -> List(b) { - do_map(list, fun, []) -} + fn do_map(list: List(a), fun: fn(a) -> b, acc: List(b)) -> List(b) { + case list { + [] -> reverse(acc) + [x, ..xs] -> do_map(xs, fun, [fun(x), ..acc]) + } + } -/// Similar to map but also lets you pass around an accumulated value. -/// -/// ## Examples -/// -/// ``` -/// > map_fold( -/// over: [1, 2, 3], -/// from: 100, -/// with: fn(i, memo) { #(i * 2, memo + i) } -/// ) -/// #([2, 4, 6], 106) -/// ``` -/// -pub fn map_fold( - over list: List(a), - from memo: memo, - with fun: fn(a, memo) -> #(b, memo), -) -> #(List(b), memo) { - fold( - over: list, - from: #([], memo), - with: fn(item, acc) { - let #(items, current_memo) = acc - let #(next_item, next_memo) = fun(item, current_memo) - #([next_item, ..items], next_memo) - }, - ) - |> pair.map_first(reverse) -} + /// Returns a new list containing only the elements of the first list after the + /// function has been applied to each one. + /// + /// ## Examples + /// + /// > map([2, 4, 6], fn(x) { x * 2 }) + /// [4, 8, 12] + /// + pub fn map(list: List(a), with fun: fn(a) -> b) -> List(b) { + do_map(list, fun, []) + } -fn do_index_map( - list: List(a), - fun: fn(Int, a) -> b, - index: Int, - acc: List(b), -) -> List(b) { - case list { - [] -> reverse(acc) - [x, ..xs] -> do_index_map(xs, fun, index + 1, [fun(index, x), ..acc]) + /// Similar to map but also lets you pass around an accumulated value. + /// + /// ## Examples + /// + /// ``` + /// > map_fold( + /// over: [1, 2, 3], + /// from: 100, + /// with: fn(i, memo) { #(i * 2, memo + i) } + /// ) + /// #([2, 4, 6], 106) + /// ``` + /// + pub fn map_fold( + over list: List(a), + from memo: memo, + with fun: fn(a, memo) -> #(b, memo), + ) -> #(List(b), memo) { + fold( + over: list, + from: #([], memo), + with: fn(item, acc) { + let #(items, current_memo) = acc + let #(next_item, next_memo) = fun(item, current_memo) + #([next_item, ..items], next_memo) + }, + ) + |> pair.map_first(reverse) } -} -/// Returns a new list containing only the elements of the first list after the -/// function has been applied to each one and their index. -/// -/// The index starts at 0, so the first element is 0, the second is 1, and so -/// on. -/// -/// ## Examples -/// -/// > index_map(["a", "b"], fn(i, x) { #(i, x) }) -/// [#(0, "a"), #(1, "b")] -/// -pub fn index_map(list: List(a), with fun: fn(Int, a) -> b) -> List(b) { - do_index_map(list, fun, 0, []) -} + fn do_index_map( + list: List(a), + fun: fn(Int, a) -> b, + index: Int, + acc: List(b), + ) -> List(b) { + case list { + [] -> reverse(acc) + [x, ..xs] -> do_index_map(xs, fun, index + 1, [fun(index, x), ..acc]) + } + } -fn do_try_map( - list: List(a), - fun: fn(a) -> Result(b, e), - acc: List(b), -) -> Result(List(b), e) { - case list { - [] -> Ok(reverse(acc)) - [x, ..xs] -> - case fun(x) { - Ok(y) -> do_try_map(xs, fun, [y, ..acc]) - Error(error) -> Error(error) - } + /// Returns a new list containing only the elements of the first list after the + /// function has been applied to each one and their index. + /// + /// The index starts at 0, so the first element is 0, the second is 1, and so + /// on. + /// + /// ## Examples + /// + /// > index_map(["a", "b"], fn(i, x) { #(i, x) }) + /// [#(0, "a"), #(1, "b")] + /// + pub fn index_map(list: List(a), with fun: fn(Int, a) -> b) -> List(b) { + do_index_map(list, fun, 0, []) } -} -/// Takes a function that returns a Result applies it to each element in a -/// given list in tern. -/// -/// If the function returns `Ok(new_value)` for all elements in the list then a -/// list of the new values is returned. -/// -/// If the function returns `Error(reason)` for any of the elements then it is -/// returned immediately. None of the elements in the list are processed after -/// one returns an `Error`. -/// -/// ## Examples -/// -/// > try_map([1, 2, 3], fn(x) { Ok(x + 2) }) -/// Ok([3, 4, 5]) -/// -/// > try_map([1, 2, 3], fn(x) { Error(0) }) -/// Error(0) -/// -/// > try_map([[1], [2, 3]], head) -/// Ok([1, 2]) -/// -/// > try_map([[1], [], [2]], head) -/// Error(Nil) -/// -pub fn try_map( - over list: List(a), - with fun: fn(a) -> Result(b, e), -) -> Result(List(b), e) { - do_try_map(list, fun, []) -} + fn do_try_map( + list: List(a), + fun: fn(a) -> Result(b, e), + acc: List(b), + ) -> Result(List(b), e) { + case list { + [] -> Ok(reverse(acc)) + [x, ..xs] -> + case fun(x) { + Ok(y) -> do_try_map(xs, fun, [y, ..acc]) + Error(error) -> Error(error) + } + } + } -/// Returns a list that is the given list with up to the given number of -/// elements removed from the front of the list. -/// -/// If the element has less than the number of elements an empty list is -/// returned. -/// -/// This function runs in linear time but does not copy the list. -/// -/// ## Examples -/// -/// > drop([1, 2, 3, 4], 2) -/// [3, 4] -/// -/// > drop([1, 2, 3, 4], 9) -/// [] -/// -pub fn drop(from list: List(a), up_to n: Int) -> List(a) { - case n <= 0 { - True -> list - False -> - case list { - [] -> [] - [_, ..xs] -> drop(xs, n - 1) - } + /// Takes a function that returns a Result applies it to each element in a + /// given list in tern. + /// + /// If the function returns `Ok(new_value)` for all elements in the list then a + /// list of the new values is returned. + /// + /// If the function returns `Error(reason)` for any of the elements then it is + /// returned immediately. None of the elements in the list are processed after + /// one returns an `Error`. + /// + /// ## Examples + /// + /// > try_map([1, 2, 3], fn(x) { Ok(x + 2) }) + /// Ok([3, 4, 5]) + /// + /// > try_map([1, 2, 3], fn(x) { Error(0) }) + /// Error(0) + /// + /// > try_map([[1], [2, 3]], head) + /// Ok([1, 2]) + /// + /// > try_map([[1], [], [2]], head) + /// Error(Nil) + /// + pub fn try_map( + over list: List(a), + with fun: fn(a) -> Result(b, e), + ) -> Result(List(b), e) { + do_try_map(list, fun, []) } -} -fn do_take(list: List(a), n: Int, acc: List(a)) -> List(a) { - case n <= 0 { - True -> reverse(acc) - False -> - case list { - [] -> reverse(acc) - [x, ..xs] -> do_take(xs, n - 1, [x, ..acc]) - } + /// Returns a list that is the given list with up to the given number of + /// elements removed from the front of the list. + /// + /// If the element has less than the number of elements an empty list is + /// returned. + /// + /// This function runs in linear time but does not copy the list. + /// + /// ## Examples + /// + /// > drop([1, 2, 3, 4], 2) + /// [3, 4] + /// + /// > drop([1, 2, 3, 4], 9) + /// [] + /// + pub fn drop(from list: List(a), up_to n: Int) -> List(a) { + case n <= 0 { + True -> list + False -> + case list { + [] -> [] + [_, ..xs] -> drop(xs, n - 1) + } + } } -} -/// Returns a list containing the first given number of elements from the given -/// list. -/// -/// If the element has less than the number of elements then the full list is -/// returned. -/// -/// This function runs in linear time but does not copy the list. -/// -/// ## Examples -/// -/// > take([1, 2, 3, 4], 2) -/// [1, 2] -/// -/// > take([1, 2, 3, 4], 9) -/// [1, 2, 3, 4] -/// -pub fn take(from list: List(a), up_to n: Int) -> List(a) { - do_take(list, n, []) -} + fn do_take(list: List(a), n: Int, acc: List(a)) -> List(a) { + case n <= 0 { + True -> reverse(acc) + False -> + case list { + [] -> reverse(acc) + [x, ..xs] -> do_take(xs, n - 1, [x, ..acc]) + } + } + } -/// Returns a new empty list. -/// -/// ## Examples -/// -/// > new() -/// [] -/// -pub fn new() -> List(a) { - [] -} + /// Returns a list containing the first given number of elements from the given + /// list. + /// + /// If the element has less than the number of elements then the full list is + /// returned. + /// + /// This function runs in linear time but does not copy the list. + /// + /// ## Examples + /// + /// > take([1, 2, 3, 4], 2) + /// [1, 2] + /// + /// > take([1, 2, 3, 4], 9) + /// [1, 2, 3, 4] + /// + pub fn take(from list: List(a), up_to n: Int) -> List(a) { + do_take(list, n, []) + } -/// Joins one list onto the end of another. -/// -/// This function runs in linear time, and it traverses and copies the first -/// list. -/// -/// ## Examples -/// -/// > append([1, 2], [3]) -/// [1, 2, 3] -/// -pub external fn append(List(a), List(a)) -> List(a) = - "lists" "append" - -fn do_flatten(lists: List(List(a)), acc: List(a)) -> List(a) { - case lists { - [] -> acc - [l, ..rest] -> do_flatten(rest, append(acc, l)) + /// Returns a new empty list. + /// + /// ## Examples + /// + /// > new() + /// [] + /// + pub fn new() -> List(a) { + [] } -} -/// Flattens a list of lists into a single list. -/// -/// This function runs in linear time, and it traverses and copies all the -/// inner lists. -/// -/// ## Examples -/// -/// > flatten([[1], [2, 3], []]) -/// [1, 2, 3] -/// -pub fn flatten(lists: List(List(a))) -> List(a) { - do_flatten(lists, []) -} + /// Joins one list onto the end of another. + /// + /// This function runs in linear time, and it traverses and copies the first + /// list. + /// + /// ## Examples + /// + /// > append([1, 2], [3]) + /// [1, 2, 3] + /// + pub external fn append(List(a), List(a)) -> List(a) = + "lists" "append" + + fn do_flatten(lists: List(List(a)), acc: List(a)) -> List(a) { + case lists { + [] -> acc + [l, ..rest] -> do_flatten(rest, append(acc, l)) + } + } -/// Map and flatten the result -/// -/// ## Examples -/// -/// ``` -/// > flat_map([2, 4, 6], fn(x) { [x, x + 1] }) -/// [2, 3, 4, 5, 6, 7] -/// ``` -/// -pub fn flat_map(over list: List(a), with fun: fn(a) -> List(b)) -> List(b) { - map(list, fun) - |> flatten -} + /// Flattens a list of lists into a single list. + /// + /// This function runs in linear time, and it traverses and copies all the + /// inner lists. + /// + /// ## Examples + /// + /// > flatten([[1], [2, 3], []]) + /// [1, 2, 3] + /// + pub fn flatten(lists: List(List(a))) -> List(a) { + do_flatten(lists, []) + } -/// Reduces a list of elements into a single value by calling a given function -/// on each element, going from left to right. -/// -/// `fold([1, 2, 3], 0, add)` is the equivalent of `add(3, add(2, add(1, 0)))`. -/// -/// This function runs in linear time. -/// -pub fn fold(over list: List(a), from initial: b, with fun: fn(a, b) -> b) -> b { - case list { - [] -> initial - [x, ..rest] -> fold(rest, fun(x, initial), fun) + /// Map and flatten the result + /// + /// ## Examples + /// + /// ``` + /// > flat_map([2, 4, 6], fn(x) { [x, x + 1] }) + /// [2, 3, 4, 5, 6, 7] + /// ``` + /// + pub fn flat_map(over list: List(a), with fun: fn(a) -> List(b)) -> List(b) { + map(list, fun) + |> flatten } -} -/// Reduces a list of elements into a single value by calling a given function -/// on each element, going from right to left. -/// -/// `fold_right([1, 2, 3], 0, add)` is the equivalent of -/// `add(1, add(2, add(3, 0)))`. -/// -/// This function runs in linear time. -/// -/// Unlike `fold` this function is not tail recursive. Where possible use -/// `fold` instead as it will use less memory. -/// -pub fn fold_right( - over list: List(a), - from initial: b, - with fun: fn(a, b) -> b, -) -> b { - case list { - [] -> initial - [x, ..rest] -> fun(x, fold_right(rest, initial, fun)) + /// Reduces a list of elements into a single value by calling a given function + /// on each element, going from left to right. + /// + /// `fold([1, 2, 3], 0, add)` is the equivalent of `add(3, add(2, add(1, 0)))`. + /// + /// This function runs in linear time. + /// + pub fn fold(over list: List(a), from initial: b, with fun: fn(a, b) -> b) -> b { + case list { + [] -> initial + [x, ..rest] -> fold(rest, fun(x, initial), fun) + } } -} -fn do_index_fold( - over: List(a), - acc: b, - with: fn(Int, a, b) -> b, - index: Int, -) -> b { - case over { - [] -> acc - [first, ..rest] -> - do_index_fold(rest, with(index, first, acc), with, index + 1) + /// Reduces a list of elements into a single value by calling a given function + /// on each element, going from right to left. + /// + /// `fold_right([1, 2, 3], 0, add)` is the equivalent of + /// `add(1, add(2, add(3, 0)))`. + /// + /// This function runs in linear time. + /// + /// Unlike `fold` this function is not tail recursive. Where possible use + /// `fold` instead as it will use less memory. + /// + pub fn fold_right( + over list: List(a), + from initial: b, + with fun: fn(a, b) -> b, + ) -> b { + case list { + [] -> initial + [x, ..rest] -> fun(x, fold_right(rest, initial, fun)) + } } -} -/// Like fold but the folding function also receives the index of the current element. -/// -/// ## Examples -/// -/// ``` -/// ["a", "b", "c"] -/// |> list.index_fold([], fn(index, item, acc) { ... }) -/// ``` -/// -pub fn index_fold( - over over: List(a), - from initial: b, - with fun: fn(Int, a, b) -> b, -) -> b { - do_index_fold(over, initial, fun, 0) -} + fn do_index_fold( + over: List(a), + acc: b, + with: fn(Int, a, b) -> b, + index: Int, + ) -> b { + case over { + [] -> acc + [first, ..rest] -> + do_index_fold(rest, with(index, first, acc), with, index + 1) + } + } -/// A variant of fold that might fail. -/// -/// The folding function should return `Result(accumulator, error) -/// If the returned value is `Ok(accumulator)` try_fold will try the next value in the list. -/// If the returned value is `Error(error)` try_fold will stop and return that error. -/// -/// ## Examples -/// -/// ``` -/// [1, 2, 3, 4] -/// |> try_fold(0, fn(i, acc) { -/// case i < 3 { -/// True -> Ok(acc + i) -/// False -> Error(Nil) -/// } -/// }) -/// ``` -/// -pub fn try_fold( - over collection: List(a), - from accumulator: b, - with fun: fn(a, b) -> Result(b, e), -) -> Result(b, e) { - case collection { - [] -> Ok(accumulator) - [first, ..rest] -> - case fun(first, accumulator) { - Ok(next_accumulator) -> try_fold(rest, next_accumulator, fun) - Error(err) -> Error(err) - } + /// Like fold but the folding function also receives the index of the current element. + /// + /// ## Examples + /// + /// ``` + /// ["a", "b", "c"] + /// |> list.index_fold([], fn(index, item, acc) { ... }) + /// ``` + /// + pub fn index_fold( + over over: List(a), + from initial: b, + with fun: fn(Int, a, b) -> b, + ) -> b { + do_index_fold(over, initial, fun, 0) } -} -pub type ContinueOrStop(a) { - Continue(a) - Stop(a) -} + /// A variant of fold that might fail. + /// + /// The folding function should return `Result(accumulator, error) + /// If the returned value is `Ok(accumulator)` try_fold will try the next value in the list. + /// If the returned value is `Error(error)` try_fold will stop and return that error. + /// + /// ## Examples + /// + /// ``` + /// [1, 2, 3, 4] + /// |> try_fold(0, fn(i, acc) { + /// case i < 3 { + /// True -> Ok(acc + i) + /// False -> Error(Nil) + /// } + /// }) + /// ``` + /// + pub fn try_fold( + over collection: List(a), + from accumulator: b, + with fun: fn(a, b) -> Result(b, e), + ) -> Result(b, e) { + case collection { + [] -> Ok(accumulator) + [first, ..rest] -> + case fun(first, accumulator) { + Ok(next_accumulator) -> try_fold(rest, next_accumulator, fun) + Error(err) -> Error(err) + } + } + } -/// A variant of fold that allows to stop folding earlier. -/// -/// The folding function should return `ContinueOrStop(accumulator) -/// If the returned value is `Continue(accumulator)` fold_until will try the next value in the list. -/// If the returned value is `Stop(accumulator)` fold_until will stop and return that accumulator. -/// -/// ## Examples -/// -/// ``` -/// [1, 2, 3, 4] -/// |> fold_until(0, fn(i, acc) { -/// case i < 3 { -/// True -> Continue(acc + i) -/// False -> Stop(acc) -/// } -/// }) -/// ``` -/// -pub fn fold_until( - over collection: List(a), - from accumulator: b, - with fun: fn(a, b) -> ContinueOrStop(b), -) -> b { - case collection { - [] -> accumulator - [first, ..rest] -> - case fun(first, accumulator) { - Continue(next_accumulator) -> fold_until(rest, next_accumulator, fun) - Stop(b) -> b - } + pub type ContinueOrStop(a) { + Continue(a) + Stop(a) } -} -/// Finds the first element in a given list for which the given function returns -/// True. -/// -/// Returns `Error(Nil)` if no the function does not return True for any of the -/// elements. -/// -/// ## Examples -/// -/// > find([1, 2, 3], fn(x) { x > 2 }) -/// Ok(3) -/// -/// > find([1, 2, 3], fn(x) { x > 4 }) -/// Error(Nil) -/// -/// > find([], fn(x) { True }) -/// Error(Nil) -/// -pub fn find( - in haystack: List(a), - one_that is_desired: fn(a) -> Bool, -) -> Result(a, Nil) { - case haystack { - [] -> Error(Nil) - [x, ..rest] -> - case is_desired(x) { - True -> Ok(x) - _ -> find(in: rest, one_that: is_desired) - } + /// A variant of fold that allows to stop folding earlier. + /// + /// The folding function should return `ContinueOrStop(accumulator) + /// If the returned value is `Continue(accumulator)` fold_until will try the next value in the list. + /// If the returned value is `Stop(accumulator)` fold_until will stop and return that accumulator. + /// + /// ## Examples + /// + /// ``` + /// [1, 2, 3, 4] + /// |> fold_until(0, fn(i, acc) { + /// case i < 3 { + /// True -> Continue(acc + i) + /// False -> Stop(acc) + /// } + /// }) + /// ``` + /// + pub fn fold_until( + over collection: List(a), + from accumulator: b, + with fun: fn(a, b) -> ContinueOrStop(b), + ) -> b { + case collection { + [] -> accumulator + [first, ..rest] -> + case fun(first, accumulator) { + Continue(next_accumulator) -> fold_until(rest, next_accumulator, fun) + Stop(b) -> b + } + } } -} -/// Finds the first element in a given list for which the given function returns -/// `Ok(new_value)` and return the new value for that element. -/// -/// Returns `Error(Nil)` if no the function does not return Ok for any of the -/// elements. -/// -/// ## Examples -/// -/// > find_map([[], [2], [3]], head) -/// Ok(2) -/// -/// > find_map([[], []], head) -/// Error(Nil) -/// -/// > find_map([], head) -/// Error(Nil) -/// -pub fn find_map( - in haystack: List(a), - with fun: fn(a) -> Result(b, c), -) -> Result(b, Nil) { - case haystack { - [] -> Error(Nil) - [x, ..rest] -> - case fun(x) { - Ok(x) -> Ok(x) - _ -> find_map(in: rest, with: fun) - } + /// Finds the first element in a given list for which the given function returns + /// True. + /// + /// Returns `Error(Nil)` if no the function does not return True for any of the + /// elements. + /// + /// ## Examples + /// + /// > find([1, 2, 3], fn(x) { x > 2 }) + /// Ok(3) + /// + /// > find([1, 2, 3], fn(x) { x > 4 }) + /// Error(Nil) + /// + /// > find([], fn(x) { True }) + /// Error(Nil) + /// + pub fn find( + in haystack: List(a), + one_that is_desired: fn(a) -> Bool, + ) -> Result(a, Nil) { + case haystack { + [] -> Error(Nil) + [x, ..rest] -> + case is_desired(x) { + True -> Ok(x) + _ -> find(in: rest, one_that: is_desired) + } + } } -} -/// Returns True if the given function returns True for all the elements in -/// the given list. If the function returns False for any of the elements it -/// immediately returns False without checking the rest of the list. -/// -/// ## Examples -/// -/// > all([], fn(x) { x > 3 }) -/// True -/// -/// > all([4, 5], fn(x) { x > 3 }) -/// True -/// -/// > all([4, 3], fn(x) { x > 3 }) -/// False -/// -pub fn all(in list: List(a), satisfying predicate: fn(a) -> Bool) -> Bool { - case list { - [] -> True - [x, ..rest] -> predicate(x) && all(rest, predicate) + /// Finds the first element in a given list for which the given function returns + /// `Ok(new_value)` and return the new value for that element. + /// + /// Returns `Error(Nil)` if no the function does not return Ok for any of the + /// elements. + /// + /// ## Examples + /// + /// > find_map([[], [2], [3]], head) + /// Ok(2) + /// + /// > find_map([[], []], head) + /// Error(Nil) + /// + /// > find_map([], head) + /// Error(Nil) + /// + pub fn find_map( + in haystack: List(a), + with fun: fn(a) -> Result(b, c), + ) -> Result(b, Nil) { + case haystack { + [] -> Error(Nil) + [x, ..rest] -> + case fun(x) { + Ok(x) -> Ok(x) + _ -> find_map(in: rest, with: fun) + } + } } -} -/// Returns True if the given function returns True for any the elements in -/// the given list. If the function returns True for any of the elements it -/// immediately returns True without checking the rest of the list. -/// -/// ## Examples -/// -/// > any([], fn(x) { x > 3 }) -/// False -/// -/// > any([4, 5], fn(x) { x > 3 }) -/// False -/// -/// > any([4, 3], fn(x) { x > 3 }) -/// True -/// -/// > any([3, 4], fn(x) { x > 3 }) -/// True -/// -pub fn any(in list: List(a), satisfying predicate: fn(a) -> Bool) -> Bool { - case list { - [] -> False - [x, ..rest] -> predicate(x) || any(rest, predicate) + /// Returns True if the given function returns True for all the elements in + /// the given list. If the function returns False for any of the elements it + /// immediately returns False without checking the rest of the list. + /// + /// ## Examples + /// + /// > all([], fn(x) { x > 3 }) + /// True + /// + /// > all([4, 5], fn(x) { x > 3 }) + /// True + /// + /// > all([4, 3], fn(x) { x > 3 }) + /// False + /// + pub fn all(in list: List(a), satisfying predicate: fn(a) -> Bool) -> Bool { + case list { + [] -> True + [x, ..rest] -> predicate(x) && all(rest, predicate) + } } -} -fn do_zip(xs: List(a), ys: List(b), acc: List(#(a, b))) -> List(#(a, b)) { - case xs, ys { - [x, ..xs], [y, ..ys] -> do_zip(xs, ys, [#(x, y), ..acc]) - _, _ -> reverse(acc) + /// Returns True if the given function returns True for any the elements in + /// the given list. If the function returns True for any of the elements it + /// immediately returns True without checking the rest of the list. + /// + /// ## Examples + /// + /// > any([], fn(x) { x > 3 }) + /// False + /// + /// > any([4, 5], fn(x) { x > 3 }) + /// False + /// + /// > any([4, 3], fn(x) { x > 3 }) + /// True + /// + /// > any([3, 4], fn(x) { x > 3 }) + /// True + /// + pub fn any(in list: List(a), satisfying predicate: fn(a) -> Bool) -> Bool { + case list { + [] -> False + [x, ..rest] -> predicate(x) || any(rest, predicate) + } } -} -/// Takes two lists and returns a single list of 2 item tuples. -/// -/// If one of the lists is longer than the other the remaining elements from -/// the longer list are not used. -/// -/// ## Examples -/// -/// > zip([], []) -/// [] -/// -/// > zip([1, 2], [3]) -/// [#(1, 3)] -/// -/// > zip([1], [3, 4]) -/// [#(1, 3)] -/// -/// > zip([1, 2], [3, 4]) -/// [#(1, 3), #(2, 4)] -/// -pub fn zip(xs: List(a), ys: List(b)) -> List(#(a, b)) { - do_zip(xs, ys, []) -} + fn do_zip(xs: List(a), ys: List(b), acc: List(#(a, b))) -> List(#(a, b)) { + case xs, ys { + [x, ..xs], [y, ..ys] -> do_zip(xs, ys, [#(x, y), ..acc]) + _, _ -> reverse(acc) + } + } -/// Takes two lists and returns a single list of 2 item tuples. -/// -/// If one of the lists is longer than the other an Error is returned. -/// -/// ## Examples -/// -/// > strict_zip([], []) -/// Ok([]) -/// -/// > strict_zip([1, 2], [3]) -/// Error(LengthMismatch) -/// -/// > strict_zip([1], [3, 4]) -/// Error(LengthMismatch) -/// -/// > strict_zip([1, 2], [3, 4]) -/// Ok([#(1, 3), #(2, 4)]) -/// -pub fn strict_zip( - l1: List(a), - l2: List(b), -) -> Result(List(#(a, b)), LengthMismatch) { - case length(of: l1) == length(of: l2) { - True -> Ok(zip(l1, l2)) - False -> Error(LengthMismatch) + /// Takes two lists and returns a single list of 2 item tuples. + /// + /// If one of the lists is longer than the other the remaining elements from + /// the longer list are not used. + /// + /// ## Examples + /// + /// > zip([], []) + /// [] + /// + /// > zip([1, 2], [3]) + /// [#(1, 3)] + /// + /// > zip([1], [3, 4]) + /// [#(1, 3)] + /// + /// > zip([1, 2], [3, 4]) + /// [#(1, 3), #(2, 4)] + /// + pub fn zip(xs: List(a), ys: List(b)) -> List(#(a, b)) { + do_zip(xs, ys, []) } -} -fn do_unzip(input, xs, ys) { - case input { - [] -> #(reverse(xs), reverse(ys)) - [#(x, y), ..rest] -> do_unzip(rest, [x, ..xs], [y, ..ys]) + /// Takes two lists and returns a single list of 2 item tuples. + /// + /// If one of the lists is longer than the other an Error is returned. + /// + /// ## Examples + /// + /// > strict_zip([], []) + /// Ok([]) + /// + /// > strict_zip([1, 2], [3]) + /// Error(LengthMismatch) + /// + /// > strict_zip([1], [3, 4]) + /// Error(LengthMismatch) + /// + /// > strict_zip([1, 2], [3, 4]) + /// Ok([#(1, 3), #(2, 4)]) + /// + pub fn strict_zip( + l1: List(a), + l2: List(b), + ) -> Result(List(#(a, b)), LengthMismatch) { + case length(of: l1) == length(of: l2) { + True -> Ok(zip(l1, l2)) + False -> Error(LengthMismatch) + } } -} -/// Takes a single list of 2 item tuples and returns two lists. -/// -/// ## Examples -/// -/// > unzip([#(1, 2), #(3, 4)]) -/// #([1, 3], [2, 4]) -/// -/// > unzip([]) -/// #([], []) -/// -pub fn unzip(input: List(#(a, b))) -> #(List(a), List(b)) { - do_unzip(input, [], []) -} + fn do_unzip(input, xs, ys) { + case input { + [] -> #(reverse(xs), reverse(ys)) + [#(x, y), ..rest] -> do_unzip(rest, [x, ..xs], [y, ..ys]) + } + } -fn do_intersperse(list: List(a), separator: a, acc: List(a)) -> List(a) { - case list { - [] -> reverse(acc) - [x, ..rest] -> do_intersperse(rest, separator, [x, separator, ..acc]) + /// Takes a single list of 2 item tuples and returns two lists. + /// + /// ## Examples + /// + /// > unzip([#(1, 2), #(3, 4)]) + /// #([1, 3], [2, 4]) + /// + /// > unzip([]) + /// #([], []) + /// + pub fn unzip(input: List(#(a, b))) -> #(List(a), List(b)) { + do_unzip(input, [], []) } -} -/// Inserts a given value between each existing element in a given list. -/// -/// This function runs in linear time and copies the list. -/// -/// ## Examples -/// -/// > intersperse([1, 1, 1], 2) -/// [1, 2, 1, 2, 1] -/// -/// > intersperse([], 2) -/// [] -/// -pub fn intersperse(list: List(a), with elem: a) -> List(a) { - case list { - [] | [_] -> list - [x, ..rest] -> do_intersperse(rest, elem, [x]) + fn do_intersperse(list: List(a), separator: a, acc: List(a)) -> List(a) { + case list { + [] -> reverse(acc) + [x, ..rest] -> do_intersperse(rest, separator, [x, separator, ..acc]) + } } -} -/// Returns the element in the Nth position in the list, with 0 being the first -/// position. -/// -/// Error(Nil) is returned if the list is not long enough for the given index. -/// -/// ## Examples -/// -/// > at([1, 2, 3], 1) -/// Ok(2) -/// -/// > at([1, 2, 3], 5) -/// Error(Nil) -/// -pub fn at(in list: List(a), get index: Int) -> Result(a, Nil) { - case index < 0 { - True -> Error(Nil) - False -> - case list { - [] -> Error(Nil) - [x, ..rest] -> - case index == 0 { - True -> Ok(x) - False -> at(rest, index - 1) - } - } + /// Inserts a given value between each existing element in a given list. + /// + /// This function runs in linear time and copies the list. + /// + /// ## Examples + /// + /// > intersperse([1, 1, 1], 2) + /// [1, 2, 1, 2, 1] + /// + /// > intersperse([], 2) + /// [] + /// + pub fn intersperse(list: List(a), with elem: a) -> List(a) { + case list { + [] | [_] -> list + [x, ..rest] -> do_intersperse(rest, elem, [x]) + } } -} -/// Removes any duplicate elements from a given list. -/// -/// This function returns in log-linear time (n log n). -/// -/// ## Examples -/// -/// > unique([1, 1, 1, 4, 7, 3, 3, 4]) -/// [1, 4, 7, 3] -/// -pub fn unique(list: List(a)) -> List(a) { - case list { - [] -> [] - [x, ..rest] -> [x, ..unique(filter(rest, fn(y) { y != x }))] + /// Returns the element in the Nth position in the list, with 0 being the first + /// position. + /// + /// Error(Nil) is returned if the list is not long enough for the given index. + /// + /// ## Examples + /// + /// > at([1, 2, 3], 1) + /// Ok(2) + /// + /// > at([1, 2, 3], 5) + /// Error(Nil) + /// + pub fn at(in list: List(a), get index: Int) -> Result(a, Nil) { + case index < 0 { + True -> Error(Nil) + False -> + case list { + [] -> Error(Nil) + [x, ..rest] -> + case index == 0 { + True -> Ok(x) + False -> at(rest, index - 1) + } + } + } } -} -fn merge_sort(a: List(a), b: List(a), compare: fn(a, a) -> Order) -> List(a) { - case a, b { - [], _ -> b - _, [] -> a - [ax, ..ar], [bx, ..br] -> - case compare(ax, bx) { - order.Lt -> [ax, ..merge_sort(ar, b, compare)] - _ -> [bx, ..merge_sort(a, br, compare)] - } + /// Removes any duplicate elements from a given list. + /// + /// This function returns in log-linear time (n log n). + /// + /// ## Examples + /// + /// > unique([1, 1, 1, 4, 7, 3, 3, 4]) + /// [1, 4, 7, 3] + /// + pub fn unique(list: List(a)) -> List(a) { + case list { + [] -> [] + [x, ..rest] -> [x, ..unique(filter(rest, fn(y) { y != x }))] + } } -} -fn do_sort( - list: List(a), - compare: fn(a, a) -> Order, - list_length: Int, -) -> List(a) { - case list_length < 2 { - True -> list - False -> { - let split_length = list_length / 2 - let a_list = take(list, split_length) - let b_list = drop(list, split_length) - merge_sort( - do_sort(a_list, compare, split_length), - do_sort(b_list, compare, list_length - split_length), - compare, - ) + fn merge_sort(a: List(a), b: List(a), compare: fn(a, a) -> Order) -> List(a) { + case a, b { + [], _ -> b + _, [] -> a + [ax, ..ar], [bx, ..br] -> + case compare(ax, bx) { + order.Lt -> [ax, ..merge_sort(ar, b, compare)] + _ -> [bx, ..merge_sort(a, br, compare)] + } } } -} -/// Sorts from smallest to largest based upon the ordering specified by a given -/// function. -/// -/// ## Examples -/// -/// > import gleam/int -/// > list.sort([4, 3, 6, 5, 4, 1, 2], by: int.compare) -/// [1, 2, 3, 4, 4, 5, 6] -/// -pub fn sort(list: List(a), by compare: fn(a, a) -> Order) -> List(a) { - do_sort(list, compare, length(list)) -} + fn do_sort( + list: List(a), + compare: fn(a, a) -> Order, + list_length: Int, + ) -> List(a) { + case list_length < 2 { + True -> list + False -> { + let split_length = list_length / 2 + let a_list = take(list, split_length) + let b_list = drop(list, split_length) + merge_sort( + do_sort(a_list, compare, split_length), + do_sort(b_list, compare, list_length - split_length), + compare, + ) + } + } + } -/// Creates a list of ints ranging from a given start and finish. -/// -/// ## Examples -/// -/// > range(0, 0) -/// [] -/// -/// > range(0, 5) -/// [0, 1, 2, 3, 4] -/// -/// > range(1, -5) -/// [1, 0, -1, -2, -3, -4] -/// -pub fn range(from start: Int, to stop: Int) -> List(Int) { - case int.compare(start, stop) { - order.Eq -> [] - order.Gt -> [start, ..range(start - 1, stop)] - order.Lt -> [start, ..range(start + 1, stop)] + /// Sorts from smallest to largest based upon the ordering specified by a given + /// function. + /// + /// ## Examples + /// + /// > import gleam/int + /// > list.sort([4, 3, 6, 5, 4, 1, 2], by: int.compare) + /// [1, 2, 3, 4, 4, 5, 6] + /// + pub fn sort(list: List(a), by compare: fn(a, a) -> Order) -> List(a) { + do_sort(list, compare, length(list)) } -} -fn do_repeat(a: a, times: Int, acc: List(a)) -> List(a) { - case times <= 0 { - True -> acc - False -> do_repeat(a, times - 1, [a, ..acc]) + /// Creates a list of ints ranging from a given start and finish. + /// + /// ## Examples + /// + /// > range(0, 0) + /// [] + /// + /// > range(0, 5) + /// [0, 1, 2, 3, 4] + /// + /// > range(1, -5) + /// [1, 0, -1, -2, -3, -4] + /// + pub fn range(from start: Int, to stop: Int) -> List(Int) { + case int.compare(start, stop) { + order.Eq -> [] + order.Gt -> [start, ..range(start - 1, stop)] + order.Lt -> [start, ..range(start + 1, stop)] + } } -} -/// Builds a list of a given value a given number of times. -/// -/// ## Examples -/// -/// > repeat("a", times: 0) -/// [] -/// -/// > repeat("a", times: 5) -/// ["a", "a", "a", "a", "a"] -/// -pub fn repeat(item a: a, times times: Int) -> List(a) { - do_repeat(a, times, []) -} + fn do_repeat(a: a, times: Int, acc: List(a)) -> List(a) { + case times <= 0 { + True -> acc + False -> do_repeat(a, times - 1, [a, ..acc]) + } + } -fn do_split(list: List(a), n: Int, taken: List(a)) -> #(List(a), List(a)) { - case n <= 0 { - True -> #(reverse(taken), list) - False -> - case list { - [] -> #(reverse(taken), []) - [x, ..xs] -> do_split(xs, n - 1, [x, ..taken]) - } + /// Builds a list of a given value a given number of times. + /// + /// ## Examples + /// + /// > repeat("a", times: 0) + /// [] + /// + /// > repeat("a", times: 5) + /// ["a", "a", "a", "a", "a"] + /// + pub fn repeat(item a: a, times times: Int) -> List(a) { + do_repeat(a, times, []) } -} -/// Splits a list in two before the given index. -/// -/// If the list is not long enough to have the given index the before list will -/// be the input list, and the after list will be empty. -/// -/// ## Examples -/// -/// > split([6, 7, 8, 9], 0) -/// #([], [6, 7, 8, 9]) -/// -/// > split([6, 7, 8, 9], 2) -/// #([6, 7], [8, 9]) -/// -/// > split([6, 7, 8, 9], 4) -/// #([6, 7, 8, 9], []) -/// -pub fn split(list list: List(a), at index: Int) -> #(List(a), List(a)) { - do_split(list, index, []) -} + fn do_split(list: List(a), n: Int, taken: List(a)) -> #(List(a), List(a)) { + case n <= 0 { + True -> #(reverse(taken), list) + False -> + case list { + [] -> #(reverse(taken), []) + [x, ..xs] -> do_split(xs, n - 1, [x, ..taken]) + } + } + } -fn do_split_while( - list: List(a), - f: fn(a) -> Bool, - acc: List(a), -) -> #(List(a), List(a)) { - case list { - [] -> #(reverse(acc), []) - [x, ..xs] -> - case f(x) { - False -> #(reverse(acc), list) - _ -> do_split_while(xs, f, [x, ..acc]) - } + /// Splits a list in two before the given index. + /// + /// If the list is not long enough to have the given index the before list will + /// be the input list, and the after list will be empty. + /// + /// ## Examples + /// + /// > split([6, 7, 8, 9], 0) + /// #([], [6, 7, 8, 9]) + /// + /// > split([6, 7, 8, 9], 2) + /// #([6, 7], [8, 9]) + /// + /// > split([6, 7, 8, 9], 4) + /// #([6, 7, 8, 9], []) + /// + pub fn split(list list: List(a), at index: Int) -> #(List(a), List(a)) { + do_split(list, index, []) } -} -/// Splits a list in two before the first element that a given function returns -/// False for. -/// -/// If the function returns True for all elements the first list will be the -/// input list, and the second list will be empty. -/// -/// ## Examples -/// -/// > split_while([1, 2, 3, 4, 5], fn(x) { x <= 3 }) -/// #([1, 2, 3], [4, 5]) -/// -/// > split_while([1, 2, 3, 4, 5], fn(x) { x <= 5 }) -/// #([1, 2, 3, 4, 5], []) -/// -pub fn split_while( - list list: List(a), - satisfying predicate: fn(a) -> Bool, -) -> #(List(a), List(a)) { - do_split_while(list, predicate, []) -} + fn do_split_while( + list: List(a), + f: fn(a) -> Bool, + acc: List(a), + ) -> #(List(a), List(a)) { + case list { + [] -> #(reverse(acc), []) + [x, ..xs] -> + case f(x) { + False -> #(reverse(acc), list) + _ -> do_split_while(xs, f, [x, ..acc]) + } + } + } -/// Given a list of 2 element tuples, finds the first tuple that has a given -/// key as the first element and returns the second element. -/// -/// If no tuple is found with the given key then `Error(Nil)` is returned. -/// -/// This function may be useful for interacting with Erlang code where lists of -/// tuples are common. -/// -/// ## Examples -/// -/// > key_find([#("a", 0), #("b", 1)], "a") -/// Ok(0) -/// -/// > key_find([#("a", 0), #("b", 1)], "b") -/// Ok(1) -/// -/// > key_find([#("a", 0), #("b", 1)], "c") -/// Error(Nil) -/// -pub fn key_find( - in keyword_list: List(#(k, v)), - find desired_key: k, -) -> Result(v, Nil) { - find_map( - keyword_list, - fn(keyword) { - let #(key, value) = keyword - case key == desired_key { - True -> Ok(value) - False -> Error(Nil) - } - }, - ) -} + /// Splits a list in two before the first element that a given function returns + /// False for. + /// + /// If the function returns True for all elements the first list will be the + /// input list, and the second list will be empty. + /// + /// ## Examples + /// + /// > split_while([1, 2, 3, 4, 5], fn(x) { x <= 3 }) + /// #([1, 2, 3], [4, 5]) + /// + /// > split_while([1, 2, 3, 4, 5], fn(x) { x <= 5 }) + /// #([1, 2, 3, 4, 5], []) + /// + pub fn split_while( + list list: List(a), + satisfying predicate: fn(a) -> Bool, + ) -> #(List(a), List(a)) { + do_split_while(list, predicate, []) + } -fn do_pop(haystack, predicate, checked) { - case haystack { - [] -> Error(Nil) - [x, ..rest] -> - case predicate(x) { - True -> Ok(#(x, append(reverse(checked), rest))) - False -> do_pop(rest, predicate, [x, ..checked]) - } + /// Given a list of 2 element tuples, finds the first tuple that has a given + /// key as the first element and returns the second element. + /// + /// If no tuple is found with the given key then `Error(Nil)` is returned. + /// + /// This function may be useful for interacting with Erlang code where lists of + /// tuples are common. + /// + /// ## Examples + /// + /// > key_find([#("a", 0), #("b", 1)], "a") + /// Ok(0) + /// + /// > key_find([#("a", 0), #("b", 1)], "b") + /// Ok(1) + /// + /// > key_find([#("a", 0), #("b", 1)], "c") + /// Error(Nil) + /// + pub fn key_find( + in keyword_list: List(#(k, v)), + find desired_key: k, + ) -> Result(v, Nil) { + find_map( + keyword_list, + fn(keyword) { + let #(key, value) = keyword + case key == desired_key { + True -> Ok(value) + False -> Error(Nil) + } + }, + ) } -} -/// Removes the first element in a given list for which the predicate funtion returns `True`. -/// -/// Returns `Error(Nil)` if no the function does not return True for any of the -/// elements. -/// -/// ## Examples -/// -/// > pop([1, 2, 3], fn(x) { x > 2 }) -/// Ok(#(3, [1, 2])) -/// -/// > pop([1, 2, 3], fn(x) { x > 4 }) -/// Error(Nil) -/// -/// > pop([], fn(x) { True }) -/// Error(Nil) -/// -pub fn pop( - in haystack: List(a), - one_that is_desired: fn(a) -> Bool, -) -> Result(#(a, List(a)), Nil) { - do_pop(haystack, is_desired, []) -} + fn do_pop(haystack, predicate, checked) { + case haystack { + [] -> Error(Nil) + [x, ..rest] -> + case predicate(x) { + True -> Ok(#(x, append(reverse(checked), rest))) + False -> do_pop(rest, predicate, [x, ..checked]) + } + } + } -fn do_pop_map(haystack, mapper, checked) { - case haystack { - [] -> Error(Nil) - [x, ..rest] -> - case mapper(x) { - Ok(y) -> Ok(#(y, append(reverse(checked), rest))) - Error(_) -> do_pop_map(rest, mapper, [x, ..checked]) - } + /// Removes the first element in a given list for which the predicate funtion returns `True`. + /// + /// Returns `Error(Nil)` if no the function does not return True for any of the + /// elements. + /// + /// ## Examples + /// + /// > pop([1, 2, 3], fn(x) { x > 2 }) + /// Ok(#(3, [1, 2])) + /// + /// > pop([1, 2, 3], fn(x) { x > 4 }) + /// Error(Nil) + /// + /// > pop([], fn(x) { True }) + /// Error(Nil) + /// + pub fn pop( + in haystack: List(a), + one_that is_desired: fn(a) -> Bool, + ) -> Result(#(a, List(a)), Nil) { + do_pop(haystack, is_desired, []) } -} -/// Removes the first element in a given list for which the given function returns -/// `Ok(new_value)` and return the new value as well as list with the value removed. -/// -/// Returns `Error(Nil)` if no the function does not return Ok for any of the -/// elements. -/// -/// ## Examples -/// -/// > pop_map([[], [2], [3]], head) -/// Ok(#(2, [[], [3]])) -/// -/// > pop_map([[], []], head) -/// Error(Nil) -/// -/// > pop_map([], head) -/// Error(Nil) -/// -pub fn pop_map( - in haystack: List(a), - one_that is_desired: fn(a) -> Result(b, c), -) -> Result(#(b, List(a)), Nil) { - do_pop_map(haystack, is_desired, []) -} + fn do_pop_map(haystack, mapper, checked) { + case haystack { + [] -> Error(Nil) + [x, ..rest] -> + case mapper(x) { + Ok(y) -> Ok(#(y, append(reverse(checked), rest))) + Error(_) -> do_pop_map(rest, mapper, [x, ..checked]) + } + } + } -/// Given a list of 2 element tuples, finds the first tuple that has a given -/// key as the first element. This function will return the second element -/// of the found tuple and list with tuple removed. -/// -/// If no tuple is found with the given key then `Error(Nil)` is returned. -/// -/// ## Examples -/// -/// > key_pop([#("a", 0), #("b", 1)], "a") -/// Ok(#(0, [#("b", 1)]) -/// -/// > key_pop([#("a", 0), #("b", 1)], "b") -/// Ok(#(1, [#("a", 0)]) -/// -/// > key_pop([#("a", 0), #("b", 1)], "c") -/// Error(Nil) -/// -pub fn key_pop( - haystack: List(#(k, v)), - key: k, -) -> Result(#(v, List(#(k, v))), Nil) { - pop_map( - haystack, - fn(entry) { - let #(k, v) = entry - case k { - k if k == key -> Ok(v) - _ -> Error(Nil) - } - }, - ) -} + /// Removes the first element in a given list for which the given function returns + /// `Ok(new_value)` and return the new value as well as list with the value removed. + /// + /// Returns `Error(Nil)` if no the function does not return Ok for any of the + /// elements. + /// + /// ## Examples + /// + /// > pop_map([[], [2], [3]], head) + /// Ok(#(2, [[], [3]])) + /// + /// > pop_map([[], []], head) + /// Error(Nil) + /// + /// > pop_map([], head) + /// Error(Nil) + /// + pub fn pop_map( + in haystack: List(a), + one_that is_desired: fn(a) -> Result(b, c), + ) -> Result(#(b, List(a)), Nil) { + do_pop_map(haystack, is_desired, []) + } -/// Given a list of 2 element tuples, inserts a key and value into the list. -/// -/// If there was already a tuple with the key then it is replaced, otherwise it -/// is added to the end of the list. -/// -/// -/// ## Examples -/// -/// > key_set([#(5, 0), #(4, 1)], 4, 100) -/// [#(5, 0), #(4, 100)] -/// -/// > key_set([#(5, 0), #(4, 1)], 1, 100) -/// [#(5, 0), #(4, 1), #(1, 100)] -/// -pub fn key_set(list: List(#(a, b)), key: a, value: b) -> List(#(a, b)) { - case list { - [] -> [#(key, value)] - [#(k, _), ..rest] if k == key -> [#(key, value), ..rest] - [first, ..rest] -> [first, ..key_set(rest, key, value)] + /// Given a list of 2 element tuples, finds the first tuple that has a given + /// key as the first element. This function will return the second element + /// of the found tuple and list with tuple removed. + /// + /// If no tuple is found with the given key then `Error(Nil)` is returned. + /// + /// ## Examples + /// + /// > key_pop([#("a", 0), #("b", 1)], "a") + /// Ok(#(0, [#("b", 1)]) + /// + /// > key_pop([#("a", 0), #("b", 1)], "b") + /// Ok(#(1, [#("a", 0)]) + /// + /// > key_pop([#("a", 0), #("b", 1)], "c") + /// Error(Nil) + /// + pub fn key_pop( + haystack: List(#(k, v)), + key: k, + ) -> Result(#(v, List(#(k, v))), Nil) { + pop_map( + haystack, + fn(entry) { + let #(k, v) = entry + case k { + k if k == key -> Ok(v) + _ -> Error(Nil) + } + }, + ) } -} -/// Calls a function for each element in a list, discarding the results. -/// -pub fn each(list: List(a), f: fn(a) -> b) -> Nil { - case list { - [] -> Nil - [x, ..xs] -> { - f(x) - each(xs, f) + /// Given a list of 2 element tuples, inserts a key and value into the list. + /// + /// If there was already a tuple with the key then it is replaced, otherwise it + /// is added to the end of the list. + /// + /// + /// ## Examples + /// + /// > key_set([#(5, 0), #(4, 1)], 4, 100) + /// [#(5, 0), #(4, 100)] + /// + /// > key_set([#(5, 0), #(4, 1)], 1, 100) + /// [#(5, 0), #(4, 1), #(1, 100)] + /// + pub fn key_set(list: List(#(a, b)), key: a, value: b) -> List(#(a, b)) { + case list { + [] -> [#(key, value)] + [#(k, _), ..rest] if k == key -> [#(key, value), ..rest] + [first, ..rest] -> [first, ..key_set(rest, key, value)] } } -} -fn do_partition(list, categorise, trues, falses) { - case list { - [] -> #(reverse(trues), reverse(falses)) - [x, ..xs] -> - case categorise(x) { - True -> do_partition(xs, categorise, [x, ..trues], falses) - False -> do_partition(xs, categorise, trues, [x, ..falses]) + /// Calls a function for each element in a list, discarding the results. + /// + pub fn each(list: List(a), f: fn(a) -> b) -> Nil { + case list { + [] -> Nil + [x, ..xs] -> { + f(x) + each(xs, f) } + } } -} -pub fn partition( - list: List(a), - with categorise: fn(a) -> Bool, -) -> #(List(a), List(a)) { - do_partition(list, categorise, [], []) -} + fn do_partition(list, categorise, trues, falses) { + case list { + [] -> #(reverse(trues), reverse(falses)) + [x, ..xs] -> + case categorise(x) { + True -> do_partition(xs, categorise, [x, ..trues], falses) + False -> do_partition(xs, categorise, trues, [x, ..falses]) + } + } + } -/// Returns all the permutations of a list -/// All values must be unique -/// -/// ## Examples -/// -/// > permutations([1, 2]) -/// [[1, 2], [2, 1]] -/// -pub fn permutations(l: List(a)) -> List(List(a)) { - case l { - [] -> [[]] - _ -> - map( - l, - fn(x) { - filter(l, fn(y) { y != x }) - |> permutations - |> map(append([x], _)) - }, - ) - |> flatten + pub fn partition( + list: List(a), + with categorise: fn(a) -> Bool, + ) -> #(List(a), List(a)) { + do_partition(list, categorise, [], []) } -} -fn do_window(acc: List(List(a)), l: List(a), n: Int) -> List(List(a)) { - let window = take(l, n) + /// Returns all the permutations of a list + /// All values must be unique + /// + /// ## Examples + /// + /// > permutations([1, 2]) + /// [[1, 2], [2, 1]] + /// + pub fn permutations(l: List(a)) -> List(List(a)) { + case l { + [] -> [[]] + _ -> + map( + l, + fn(x) { + filter(l, fn(y) { y != x }) + |> permutations + |> map(append([x], _)) + }, + ) + |> flatten + } + } + + fn do_window(acc: List(List(a)), l: List(a), n: Int) -> List(List(a)) { + let window = take(l, n) - case length(window) == n { - True -> do_window([window, ..acc], drop(l, 1), n) - False -> acc + case length(window) == n { + True -> do_window([window, ..acc], drop(l, 1), n) + False -> acc + } } -} -/// Returns a list of sliding window -/// -/// ## Examples -/// -/// ``` -/// > window([1,2,3,4,5], 3) -/// [[1, 2, 3], [2, 3, 4], [3, 4, 5]] -/// -/// > window([1, 2], 4) -/// [] -/// ``` -/// -pub fn window(l: List(a), by n: Int) -> List(List(a)) { - do_window([], l, n) - |> reverse -} + /// Returns a list of sliding window + /// + /// ## Examples + /// + /// ``` + /// > window([1,2,3,4,5], 3) + /// [[1, 2, 3], [2, 3, 4], [3, 4, 5]] + /// + /// > window([1, 2], 4) + /// [] + /// ``` + /// + pub fn window(l: List(a), by n: Int) -> List(List(a)) { + do_window([], l, n) + |> reverse + } -/// Returns a list of tuples containing two contiguous elements -/// -/// ## Examples -/// -/// ``` -/// > window_by_2([1,2,3,4]) -/// [#(1, 2), #(2, 3), #(3, 4)] -/// -/// > window_by_2([1]) -/// [] -/// ``` -/// -pub fn window_by_2(l: List(a)) -> List(#(a, a)) { - zip(l, drop(l, 1)) -} + /// Returns a list of tuples containing two contiguous elements + /// + /// ## Examples + /// + /// ``` + /// > window_by_2([1,2,3,4]) + /// [#(1, 2), #(2, 3), #(3, 4)] + /// + /// > window_by_2([1]) + /// [] + /// ``` + /// + pub fn window_by_2(l: List(a)) -> List(#(a, a)) { + zip(l, drop(l, 1)) + } -/// Drops the first elements in a given list for which the predicate funtion returns `True`. -/// -/// ## Examples -/// -/// > drop_while([1, 2, 3, 4], fun (x) { x < 3 }) -/// [3, 4] -/// -pub fn drop_while( - in list: List(a), - satisfying predicate: fn(a) -> Bool, -) -> List(a) { - case list { - [] -> [] - [x, ..xs] -> - case predicate(x) { - True -> drop_while(xs, predicate) - False -> [x, ..xs] - } + /// Drops the first elements in a given list for which the predicate funtion returns `True`. + /// + /// ## Examples + /// + /// > drop_while([1, 2, 3, 4], fun (x) { x < 3 }) + /// [3, 4] + /// + pub fn drop_while( + in list: List(a), + satisfying predicate: fn(a) -> Bool, + ) -> List(a) { + case list { + [] -> [] + [x, ..xs] -> + case predicate(x) { + True -> drop_while(xs, predicate) + False -> [x, ..xs] + } + } } -} -fn do_take_while( - list: List(a), - predicate: fn(a) -> Bool, - acc: List(a), -) -> List(a) { - case list { - [] -> reverse(acc) - [head, ..tail] -> - case predicate(head) { - True -> do_take_while(tail, predicate, [head, ..acc]) - False -> reverse(acc) - } + fn do_take_while( + list: List(a), + predicate: fn(a) -> Bool, + acc: List(a), + ) -> List(a) { + case list { + [] -> reverse(acc) + [head, ..tail] -> + case predicate(head) { + True -> do_take_while(tail, predicate, [head, ..acc]) + False -> reverse(acc) + } + } } -} -/// Takes the first elements in a given list for which the predicate funtion returns `True`. -/// -/// ## Examples -/// -/// > take_while([1, 2, 3, 2, 4], fun (x) { x < 3 }) -/// [1, 2] -/// -pub fn take_while( - in list: List(a), - satisfying predicate: fn(a) -> Bool, -) -> List(a) { - do_take_while(list, predicate, []) -} + /// Takes the first elements in a given list for which the predicate funtion returns `True`. + /// + /// ## Examples + /// + /// > take_while([1, 2, 3, 2, 4], fun (x) { x < 3 }) + /// [1, 2] + /// + pub fn take_while( + in list: List(a), + satisfying predicate: fn(a) -> Bool, + ) -> List(a) { + do_take_while(list, predicate, []) + } -fn do_chunk( - list: List(a), - f: fn(a) -> key, - previous_key: key, - current_chunk: List(a), - acc: List(List(a)), -) -> List(List(a)) { - case list { - [] -> reverse([reverse(current_chunk), ..acc]) - [head, ..tail] -> { - let key = f(head) - case key == previous_key { - False -> do_chunk(tail, f, key, [head], [reverse(current_chunk), ..acc]) - True -> do_chunk(tail, f, key, [head, ..current_chunk], acc) + fn do_chunk( + list: List(a), + f: fn(a) -> key, + previous_key: key, + current_chunk: List(a), + acc: List(List(a)), + ) -> List(List(a)) { + case list { + [] -> reverse([reverse(current_chunk), ..acc]) + [head, ..tail] -> { + let key = f(head) + case key == previous_key { + False -> + do_chunk(tail, f, key, [head], [reverse(current_chunk), ..acc]) + True -> do_chunk(tail, f, key, [head, ..current_chunk], acc) + } } } } -} -/// Returns a list of chunks in which -/// the result of calling `f` on each element is the same. -/// -/// ## Examples -/// -/// > [1, 2, 2, 3, 4, 4, 6, 7, 7] |> chunk(by: fn(n) { n % 2 }) -/// [[1], [2, 2], [3], [4, 4, 6], [7, 7]] -/// -pub fn chunk(in list: List(a), by f: fn(a) -> key) -> List(List(a)) { - case list { - [] -> [] - [head, ..tail] -> do_chunk(tail, f, f(head), [head], []) + /// Returns a list of chunks in which + /// the result of calling `f` on each element is the same. + /// + /// ## Examples + /// + /// > [1, 2, 2, 3, 4, 4, 6, 7, 7] |> chunk(by: fn(n) { n % 2 }) + /// [[1], [2, 2], [3], [4, 4, 6], [7, 7]] + /// + pub fn chunk(in list: List(a), by f: fn(a) -> key) -> List(List(a)) { + case list { + [] -> [] + [head, ..tail] -> do_chunk(tail, f, f(head), [head], []) + } } -} -fn do_sized_chunk( - list: List(a), - count: Int, - left: Int, - current_chunk: List(a), - acc: List(List(a)), -) -> List(List(a)) { - case list { - [] -> - case current_chunk { - [] -> reverse(acc) - remaining -> reverse([reverse(remaining), ..acc]) - } - [head, ..tail] -> { - let chunk = [head, ..current_chunk] - case left > 1 { - False -> do_sized_chunk(tail, count, count, [], [reverse(chunk), ..acc]) - True -> do_sized_chunk(tail, count, left - 1, chunk, acc) + fn do_sized_chunk( + list: List(a), + count: Int, + left: Int, + current_chunk: List(a), + acc: List(List(a)), + ) -> List(List(a)) { + case list { + [] -> + case current_chunk { + [] -> reverse(acc) + remaining -> reverse([reverse(remaining), ..acc]) + } + [head, ..tail] -> { + let chunk = [head, ..current_chunk] + case left > 1 { + False -> + do_sized_chunk(tail, count, count, [], [reverse(chunk), ..acc]) + True -> do_sized_chunk(tail, count, left - 1, chunk, acc) + } } } } -} -/// Returns a list of chunks containing `count` elements each. -/// -/// If the last chunk does not have `count` elements, it is instead -/// 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 -/// -/// > [1, 2, 3, 4, 5, 6] |> chunk(into: 2) -/// [[1, 2], [3, 4], [5, 6]] -/// -/// > [1, 2, 3, 4, 5, 6, 7, 8] |> chunk(into: 3) -/// [[1, 2, 3], [4, 5, 6], [7, 8]] -/// -pub fn sized_chunk(in list: List(a), into count: Int) -> List(List(a)) { - do_sized_chunk(list, count, count, [], []) -} + /// Returns a list of chunks containing `count` elements each. + /// + /// If the last chunk does not have `count` elements, it is instead + /// 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 + /// + /// > [1, 2, 3, 4, 5, 6] |> chunk(into: 2) + /// [[1, 2], [3, 4], [5, 6]] + /// + /// > [1, 2, 3, 4, 5, 6, 7, 8] |> chunk(into: 3) + /// [[1, 2, 3], [4, 5, 6], [7, 8]] + /// + pub fn sized_chunk(in list: List(a), into count: Int) -> List(List(a)) { + do_sized_chunk(list, count, count, [], []) + } -/// This function acts similar to fold, but does not take an initial state. -/// Instead, it starts from the first element in the list -/// and combines it with each subsequent element in turn using the given function. -/// The function is called as fun(current_element, accumulator). -/// -/// Returns `Ok` to indicate a successful run, and `Error` if called on an empty list. -/// -/// ## Examples -/// -/// > [] |> reduce(fn(x, y) { x + y }) -/// Error(Nil) -/// -/// > [1, 2, 3, 4, 5] |> reduce(fn(x, y) { x + y }) -/// Ok(15) -/// -pub fn reduce(over list: List(a), with fun: fn(a, a) -> a) -> Result(a, Nil) { - case list { - [] -> Error(Nil) - [head, ..tail] -> - fold(tail, head, fun) - |> Ok + /// This function acts similar to fold, but does not take an initial state. + /// Instead, it starts from the first element in the list + /// and combines it with each subsequent element in turn using the given function. + /// The function is called as fun(current_element, accumulator). + /// + /// Returns `Ok` to indicate a successful run, and `Error` if called on an empty list. + /// + /// ## Examples + /// + /// > [] |> reduce(fn(x, y) { x + y }) + /// Error(Nil) + /// + /// > [1, 2, 3, 4, 5] |> reduce(fn(x, y) { x + y }) + /// Ok(15) + /// + pub fn reduce(over list: List(a), with fun: fn(a, a) -> a) -> Result(a, Nil) { + case list { + [] -> Error(Nil) + [head, ..tail] -> + fold(tail, head, fun) + |> Ok + } } -} -fn do_scan( - list: List(a), - accumulator: b, - accumulated: List(b), - fun: fn(a, b) -> b, -) -> List(b) { - case list { - [] -> reverse(accumulated) - [x, ..xs] -> { - let next = fun(x, accumulator) - do_scan(xs, next, [next, ..accumulated], fun) + fn do_scan( + list: List(a), + accumulator: b, + accumulated: List(b), + fun: fn(a, b) -> b, + ) -> List(b) { + case list { + [] -> reverse(accumulated) + [x, ..xs] -> { + let next = fun(x, accumulator) + do_scan(xs, next, [next, ..accumulated], fun) + } } } -} -/// Similar to `fold`, but yields the state of the accumulator at each stage. -/// -/// ## Examples -/// -/// > scan(over: [1, 2, 3], from: 100, with: fn(i, acc) { acc + i }) -/// [101, 103, 106] -/// -pub fn scan( - over list: List(a), - from initial: b, - with fun: fn(a, b) -> b, -) -> List(b) { - do_scan(list, initial, [], fun) -} + /// Similar to `fold`, but yields the state of the accumulator at each stage. + /// + /// ## Examples + /// + /// > scan(over: [1, 2, 3], from: 100, with: fn(i, acc) { acc + i }) + /// [101, 103, 106] + /// + pub fn scan( + over list: List(a), + from initial: b, + with fun: fn(a, b) -> b, + ) -> List(b) { + do_scan(list, initial, [], fun) + } -/// Returns the last element in the given list. -/// -/// Returns `Error(Nil)` if the list is empty. -/// -/// This function runs in linear time. -/// For a collection oriented around performant access at either end, -/// see `gleam/queue.Queue`. -/// -/// ## Examples -/// -/// > last([]) -/// Error(Nil) -/// -/// > last([1, 2, 3, 4, 5]) -/// Ok(5) -/// -pub fn last(list: List(a)) -> Result(a, Nil) { - list - |> reduce(fn(elem, _) { elem }) -} + /// Returns the last element in the given list. + /// + /// Returns `Error(Nil)` if the list is empty. + /// + /// This function runs in linear time. + /// For a collection oriented around performant access at either end, + /// see `gleam/queue.Queue`. + /// + /// ## Examples + /// + /// > last([]) + /// Error(Nil) + /// + /// > last([1, 2, 3, 4, 5]) + /// Ok(5) + /// + pub fn last(list: List(a)) -> Result(a, Nil) { + list + |> reduce(fn(elem, _) { elem }) + } -/// Return unique combinations of elements in the list -/// -/// ## Examples -/// -/// ``` -/// > combinations([1, 2, 3], 2) -/// [[1, 2], [1, 3], [2, 3]] -/// -/// > combinations([1, 2, 3, 4], 3) -/// [[1, 2, 3], [1, 2, 4], [1, 3, 4], [2, 3, 4]] -/// ``` -/// -pub fn combinations(items: List(a), by n: Int) -> List(List(a)) { - case n { - 0 -> [[]] - _ -> - case items { - [] -> [] - [x, ..xs] -> { - let first_combinations = - map(combinations(xs, n - 1), with: fn(com) { [x, ..com] }) - |> reverse - fold( - first_combinations, - combinations(xs, n), - fn(c, acc) { [c, ..acc] }, - ) + /// Return unique combinations of elements in the list + /// + /// ## Examples + /// + /// ``` + /// > combinations([1, 2, 3], 2) + /// [[1, 2], [1, 3], [2, 3]] + /// + /// > combinations([1, 2, 3, 4], 3) + /// [[1, 2, 3], [1, 2, 4], [1, 3, 4], [2, 3, 4]] + /// ``` + /// + pub fn combinations(items: List(a), by n: Int) -> List(List(a)) { + case n { + 0 -> [[]] + _ -> + case items { + [] -> [] + [x, ..xs] -> { + let first_combinations = + map(combinations(xs, n - 1), with: fn(com) { [x, ..com] }) + |> reverse + fold( + first_combinations, + combinations(xs, n), + fn(c, acc) { [c, ..acc] }, + ) + } } - } + } } -} -fn do_combination_pairs(items: List(a)) -> List(List(#(a, a))) { - case items { - [] -> [] - [x, ..xs] -> { - let first_combinations = map(xs, with: fn(other) { #(x, other) }) - [first_combinations, ..do_combination_pairs(xs)] + fn do_combination_pairs(items: List(a)) -> List(List(#(a, a))) { + case items { + [] -> [] + [x, ..xs] -> { + let first_combinations = map(xs, with: fn(other) { #(x, other) }) + [first_combinations, ..do_combination_pairs(xs)] + } } } -} -/// Return unique pair combinations of elements in the list -/// -/// ## Examples -/// -/// ``` -/// > combination_pairs([1, 2, 3]) -/// [#(1, 2), #(1, 3), #(2, 3)] -/// ``` -/// -pub fn combination_pairs(items: List(a)) -> List(#(a, a)) { - do_combination_pairs(items) - |> flatten -} + /// Return unique pair combinations of elements in the list + /// + /// ## Examples + /// + /// ``` + /// > combination_pairs([1, 2, 3]) + /// [#(1, 2), #(1, 3), #(2, 3)] + /// ``` + /// + pub fn combination_pairs(items: List(a)) -> List(#(a, a)) { + do_combination_pairs(items) + |> flatten + } -/// Make a list alternating the elements from the given lists -/// -/// ## Examples -/// -/// ``` -/// > list.interleave([[1, 2], [101, 102], [201, 202]]) -/// [1, 101, 201, 2, 102, 202] -/// ``` -/// -pub fn interleave(list: List(List(a))) -> List(a) { - transpose(list) - |> flatten -} + /// Make a list alternating the elements from the given lists + /// + /// ## Examples + /// + /// ``` + /// > list.interleave([[1, 2], [101, 102], [201, 202]]) + /// [1, 101, 201, 2, 102, 202] + /// ``` + /// + pub fn interleave(list: List(List(a))) -> List(a) { + transpose(list) + |> flatten + } -/// Transpose rows and columns of the list of lists. -/// -/// ## Examples -/// -/// ``` -/// > transpose([[1, 2, 3], [101, 102, 103]]) -/// [[1, 101], [2, 102], [3, 103]] -/// ``` -pub fn transpose(list_of_list: List(List(a))) -> List(List(a)) { - let take_first = fn(list) { - case list { - [] -> [] - [f] -> [f] - [f, .._rest] -> [f] + /// Transpose rows and columns of the list of lists. + /// + /// ## Examples + /// + /// ``` + /// > transpose([[1, 2, 3], [101, 102, 103]]) + /// [[1, 101], [2, 102], [3, 103]] + /// ``` + pub fn transpose(list_of_list: List(List(a))) -> List(List(a)) { + let take_first = fn(list) { + case list { + [] -> [] + [f] -> [f] + [f, .._rest] -> [f] + } } - } - case list_of_list { - [] -> [] - [[], ..xss] -> transpose(xss) - rows -> { - let firsts = - rows - |> map(take_first) - |> flatten - let rest = transpose(map(rows, drop(_, 1))) - [firsts, ..rest] + case list_of_list { + [] -> [] + [[], ..xss] -> transpose(xss) + rows -> { + let firsts = + rows + |> map(take_first) + |> flatten + let rest = transpose(map(rows, drop(_, 1))) + [firsts, ..rest] + } } } } diff --git a/src/gleam/map.gleam b/src/gleam/map.gleam index d51a78e..ff8281d 100644 --- a/src/gleam/map.gleam +++ b/src/gleam/map.gleam @@ -1,321 +1,327 @@ -import gleam/result -import gleam/list +if erlang { + import gleam/result + import gleam/list -/// A dictionary of keys and values. -/// -/// Any type can be used for the keys and values of a map, but all the keys -/// must be of the same type and all the values must be of the same type. -/// -/// Each key can only be present in a map once. -/// -/// Maps are not ordered in any way, and any unintentional ordering is not to -/// be relied upon in your code as it may change in future versions of Erlang -/// or Gleam. -/// -/// See [the Erlang map module](https://erlang.org/doc/man/maps.html) for more -/// information. -/// -pub external type Map(key, value) + /// A dictionary of keys and values. + /// + /// Any type can be used for the keys and values of a map, but all the keys + /// must be of the same type and all the values must be of the same type. + /// + /// Each key can only be present in a map once. + /// + /// Maps are not ordered in any way, and any unintentional ordering is not to + /// be relied upon in your code as it may change in future versions of Erlang + /// or Gleam. + /// + /// See [the Erlang map module](https://erlang.org/doc/man/maps.html) for more + /// information. + /// + pub external type Map(key, value) -/// Determines the number of key-value pairs in the map. -/// This function runs in constant time and does not need to iterate the map. -/// -/// ## Examples -/// -/// > new() |> size() -/// 0 -/// -/// > new() |> insert("key", "value") |> size() -/// 1 -/// -/// -pub external fn size(Map(k, v)) -> Int = - "maps" "size" + /// Determines the number of key-value pairs in the map. + /// This function runs in constant time and does not need to iterate the map. + /// + /// ## Examples + /// + /// > new() |> size() + /// 0 + /// + /// > new() |> insert("key", "value") |> size() + /// 1 + /// + /// + pub external fn size(Map(k, v)) -> Int = + "maps" "size" -/// Converts the map to a list of 2-element tuples `#(key, value)`, one for -/// each key-value pair in the map. -/// -/// The tuples in the list have no specific order. -/// -/// ## Examples -/// -/// > new() |> to_list() -/// [] -/// -/// > new() |> insert("key", 0) |> to_list() -/// [#("key", 0)] -/// -pub external fn to_list(Map(key, value)) -> List(#(key, value)) = - "maps" "to_list" + /// Converts the map to a list of 2-element tuples `#(key, value)`, one for + /// each key-value pair in the map. + /// + /// The tuples in the list have no specific order. + /// + /// ## Examples + /// + /// > new() |> to_list() + /// [] + /// + /// > new() |> insert("key", 0) |> to_list() + /// [#("key", 0)] + /// + pub external fn to_list(Map(key, value)) -> List(#(key, value)) = + "maps" "to_list" -/// Converts a list of 2-element tuples `#(key, value)` to a map. -/// -/// If two tuples have the same key the last one in the list will be the one -/// that is present in the map. -/// -pub external fn from_list(List(#(key, value))) -> Map(key, value) = - "maps" "from_list" + /// Converts a list of 2-element tuples `#(key, value)` to a map. + /// + /// If two tuples have the same key the last one in the list will be the one + /// that is present in the map. + /// + pub external fn from_list(List(#(key, value))) -> Map(key, value) = + "maps" "from_list" -external fn is_key(key, Map(key, v)) -> Bool = - "maps" "is_key" + external fn is_key(key, Map(key, v)) -> Bool = + "maps" "is_key" -/// Determines whether or not a value present in the map for a given key. -/// -/// ## Examples -/// -/// > new() |> insert("a", 0) |> has_key("a") -/// True -/// -/// > new() |> insert("a", 0) |> has_key("b") -/// False -/// -pub fn has_key(map: Map(k, v), key: k) -> Bool { - is_key(key, map) -} + /// Determines whether or not a value present in the map for a given key. + /// + /// ## Examples + /// + /// > new() |> insert("a", 0) |> has_key("a") + /// True + /// + /// > new() |> insert("a", 0) |> has_key("b") + /// False + /// + pub fn has_key(map: Map(k, v), key: k) -> Bool { + is_key(key, map) + } -/// Creates a fresh map that contains no values. -/// -pub external fn new() -> Map(key, value) = - "maps" "new" + /// Creates a fresh map that contains no values. + /// + pub external fn new() -> Map(key, value) = + "maps" "new" -/// Fetches a value from a map for a given key. -/// -/// The map may not have a value for the key, so the value is wrapped in a -/// Result. -/// -/// ## Examples -/// -/// > new() |> insert("a", 0) |> get("a") -/// Ok(0) -/// -/// > new() |> insert("a", 0) |> get("b") -/// Error(Nil) -/// -pub external fn get(from: Map(key, value), get: key) -> Result(value, Nil) = - "gleam_stdlib" "map_get" + /// Fetches a value from a map for a given key. + /// + /// The map may not have a value for the key, so the value is wrapped in a + /// Result. + /// + /// ## Examples + /// + /// > new() |> insert("a", 0) |> get("a") + /// Ok(0) + /// + /// > new() |> insert("a", 0) |> get("b") + /// Error(Nil) + /// + pub external fn get(from: Map(key, value), get: key) -> Result(value, Nil) = + "gleam_stdlib" "map_get" -external fn erl_insert(key, value, Map(key, value)) -> Map(key, value) = - "maps" "put" + external fn erl_insert(key, value, Map(key, value)) -> Map(key, value) = + "maps" "put" -/// Inserts a value into the map with the given key. -/// -/// If the map already has a value for the given key then the value is -/// replaced with the new value. -/// -/// ## Examples -/// -/// > new() |> insert("a", 0) |> to_list -/// [#("a", 0)] -/// -/// > new() |> insert("a", 0) |> insert("a", 5) |> to_list -/// [#("a", 5)] -/// -pub fn insert(into map: Map(k, v), for key: k, insert value: v) -> Map(k, v) { - erl_insert(key, value, map) -} + /// Inserts a value into the map with the given key. + /// + /// If the map already has a value for the given key then the value is + /// replaced with the new value. + /// + /// ## Examples + /// + /// > new() |> insert("a", 0) |> to_list + /// [#("a", 0)] + /// + /// > new() |> insert("a", 0) |> insert("a", 5) |> to_list + /// [#("a", 5)] + /// + pub fn insert(into map: Map(k, v), for key: k, insert value: v) -> Map(k, v) { + erl_insert(key, value, map) + } -external fn erl_map_values(fn(key, a) -> b, Map(key, value)) -> Map(key, b) = - "maps" "map" + external fn erl_map_values(fn(key, a) -> b, Map(key, value)) -> Map(key, b) = + "maps" "map" -/// Updates all values in a given map by calling a given function on each key -/// and value. -/// -/// ## Examples -/// -/// > [#(3, 3), #(2, 4)] -/// > |> from_list -/// > |> map_values(fn(key, value) { key * value }) -/// [#(3, 9), #(2, 8)] -/// -/// -pub fn map_values(in map: Map(k, v), with fun: fn(k, v) -> w) -> Map(k, w) { - erl_map_values(fun, map) -} + /// Updates all values in a given map by calling a given function on each key + /// and value. + /// + /// ## Examples + /// + /// > [#(3, 3), #(2, 4)] + /// > |> from_list + /// > |> map_values(fn(key, value) { key * value }) + /// [#(3, 9), #(2, 8)] + /// + /// + pub fn map_values(in map: Map(k, v), with fun: fn(k, v) -> w) -> Map(k, w) { + erl_map_values(fun, map) + } -/// Gets a list of all keys in a given map. -/// -/// Maps are not ordered so the keys are not returned in any specific order. Do -/// not write code that relies on the order keys are returned by this function -/// as it may change in later versions of Gleam or Erlang. -/// -/// ## Examples -/// -/// > keys([#("a", 0), #("b", 1)]) -/// ["a", "b"] -/// -pub external fn keys(Map(keys, v)) -> List(keys) = - "maps" "keys" + /// Gets a list of all keys in a given map. + /// + /// Maps are not ordered so the keys are not returned in any specific order. Do + /// not write code that relies on the order keys are returned by this function + /// as it may change in later versions of Gleam or Erlang. + /// + /// ## Examples + /// + /// > keys([#("a", 0), #("b", 1)]) + /// ["a", "b"] + /// + pub external fn keys(Map(keys, v)) -> List(keys) = + "maps" "keys" -/// Gets a list of all values in a given map. -/// -/// Maps are not ordered so the values are not returned in any specific order. Do -/// not write code that relies on the order values are returned by this function -/// as it may change in later versions of Gleam or Erlang. -/// -/// ## Examples -/// -/// > keys(from_list([#("a", 0), #("b", 1)])) -/// [0, 1] -/// -pub external fn values(Map(k, values)) -> List(values) = - "maps" "values" + /// Gets a list of all values in a given map. + /// + /// Maps are not ordered so the values are not returned in any specific order. Do + /// not write code that relies on the order values are returned by this function + /// as it may change in later versions of Gleam or Erlang. + /// + /// ## Examples + /// + /// > keys(from_list([#("a", 0), #("b", 1)])) + /// [0, 1] + /// + pub external fn values(Map(k, values)) -> List(values) = + "maps" "values" -external fn erl_filter( - fn(key, value) -> Bool, - Map(key, value), -) -> Map(key, value) = - "maps" "filter" + external fn erl_filter( + fn(key, value) -> Bool, + Map(key, value), + ) -> Map(key, value) = + "maps" "filter" -/// Creates a new map from a given map, minus any entries that a given function -/// returns False for. -/// -/// ## Examples -/// -/// > from_list([#("a", 0), #("b", 1)]) -/// > |> filter(fn(key, value) { value != 0 }) -/// from_list([#("b", 1)]) -/// -/// > from_list([#("a", 0), #("b", 1)]) -/// > |> filter(fn(key, value) { True }) -/// from_list([#("a", 0), #("b", 1)]) -/// -pub fn filter(in map: Map(k, v), for property: fn(k, v) -> Bool) -> Map(k, v) { - erl_filter(property, map) -} + /// Creates a new map from a given map, minus any entries that a given function + /// returns False for. + /// + /// ## Examples + /// + /// > from_list([#("a", 0), #("b", 1)]) + /// > |> filter(fn(key, value) { value != 0 }) + /// from_list([#("b", 1)]) + /// + /// > from_list([#("a", 0), #("b", 1)]) + /// > |> filter(fn(key, value) { True }) + /// from_list([#("a", 0), #("b", 1)]) + /// + pub fn filter(in map: Map(k, v), for property: fn(k, v) -> Bool) -> Map(k, v) { + erl_filter(property, map) + } -external fn erl_take(List(k), Map(k, v)) -> Map(k, v) = - "maps" "with" + external fn erl_take(List(k), Map(k, v)) -> Map(k, v) = + "maps" "with" -/// Creates a new map from a given map, only including any entries for which the -/// keys are in a given list. -/// -/// ## Examples -/// -/// > from_list([#("a", 0), #("b", 1)]) -/// > |> take(["b"]) -/// from_list([#("b", 1)]) -/// -/// > from_list([#("a", 0), #("b", 1)]) -/// > |> take(["a", "b", "c"]) -/// from_list([#("a", 0), #("b", 1)]) -/// -pub fn take(from map: Map(k, v), keeping desired_keys: List(k)) -> Map(k, v) { - erl_take(desired_keys, map) -} + /// Creates a new map from a given map, only including any entries for which the + /// keys are in a given list. + /// + /// ## Examples + /// + /// > from_list([#("a", 0), #("b", 1)]) + /// > |> take(["b"]) + /// from_list([#("b", 1)]) + /// + /// > from_list([#("a", 0), #("b", 1)]) + /// > |> take(["a", "b", "c"]) + /// from_list([#("a", 0), #("b", 1)]) + /// + pub fn take(from map: Map(k, v), keeping desired_keys: List(k)) -> Map(k, v) { + erl_take(desired_keys, map) + } -/// Creates a new map from a pair of given maps by combining their entries. -/// -/// If there are entries with the same keys in both maps the entry from the -/// second map takes precedence. -/// -/// ## Examples -/// -/// > let a = from_list([#("a", 0), #("b", 1)]) -/// > let b = from_list([#("b", 2), #("c", 3)]) -/// > merge(a, b) -/// from_list([#("a", 0), #("b", 2), #("c", 3)]) -/// -pub external fn merge(into: Map(k, v), merge: Map(k, v)) -> Map(k, v) = - "maps" "merge" + /// Creates a new map from a pair of given maps by combining their entries. + /// + /// If there are entries with the same keys in both maps the entry from the + /// second map takes precedence. + /// + /// ## Examples + /// + /// > let a = from_list([#("a", 0), #("b", 1)]) + /// > let b = from_list([#("b", 2), #("c", 3)]) + /// > merge(a, b) + /// from_list([#("a", 0), #("b", 2), #("c", 3)]) + /// + pub external fn merge(into: Map(k, v), merge: Map(k, v)) -> Map(k, v) = + "maps" "merge" -external fn erl_delete(k, Map(k, v)) -> Map(k, v) = - "maps" "remove" + external fn erl_delete(k, Map(k, v)) -> Map(k, v) = + "maps" "remove" -/// Creates a new map from a given map with all the same entries except for the -/// one with a given key, if it exists. -/// -/// ## Examples -/// -/// > delete([#("a", 0), #("b", 1)], "a") -/// from_list([#("b", 1)]) -/// -/// > delete([#("a", 0), #("b", 1)], "c") -/// from_list([#("a", 0), #("b", 1)]) -/// -pub fn delete(from map: Map(k, v), delete key: k) -> Map(k, v) { - erl_delete(key, map) -} + /// Creates a new map from a given map with all the same entries except for the + /// one with a given key, if it exists. + /// + /// ## Examples + /// + /// > delete([#("a", 0), #("b", 1)], "a") + /// from_list([#("b", 1)]) + /// + /// > delete([#("a", 0), #("b", 1)], "c") + /// from_list([#("a", 0), #("b", 1)]) + /// + pub fn delete(from map: Map(k, v), delete key: k) -> Map(k, v) { + erl_delete(key, map) + } -/// Creates a new map from a given map with all the same entries except any with -/// keys found in a given list. -/// -/// ## Examples -/// -/// > drop([#("a", 0), #("b", 1)], ["a"]) -/// from_list([#("b", 2)]) -/// -/// > delete([#("a", 0), #("b", 1)], ["c"]) -/// from_list([#("a", 0), #("b", 1)]) -/// -/// > drop([#("a", 0), #("b", 1)], ["a", "b", "c"]) -/// from_list([]) -/// -pub fn drop(from map: Map(k, v), drop disallowed_keys: List(k)) -> Map(k, v) { - list.fold(disallowed_keys, map, fn(key, acc) { delete(acc, key) }) -} + /// Creates a new map from a given map with all the same entries except any with + /// keys found in a given list. + /// + /// ## Examples + /// + /// > drop([#("a", 0), #("b", 1)], ["a"]) + /// from_list([#("b", 2)]) + /// + /// > delete([#("a", 0), #("b", 1)], ["c"]) + /// from_list([#("a", 0), #("b", 1)]) + /// + /// > drop([#("a", 0), #("b", 1)], ["a", "b", "c"]) + /// from_list([]) + /// + pub fn drop(from map: Map(k, v), drop disallowed_keys: List(k)) -> Map(k, v) { + list.fold(disallowed_keys, map, fn(key, acc) { delete(acc, key) }) + } -/// Creates a new map with one entry updated using a given function. -/// -/// If there was not an entry in the map for the given key then the function -/// gets `Error(Nil)` as its argument, otherwise it gets `Ok(value)`. -/// -/// ## Example -/// -/// > let increment = fn(x) { -/// > case x { -/// > Ok(i) -> i + 1 -/// > Error(Nil) -> 0 -/// > } -/// > } -/// > let map = from_list([#("a", 0)]) -/// > -/// > update(map, "a" increment) -/// from_list([#("a", 1)]) -/// -/// > update(map, "b" increment) -/// from_list([#("a", 0), #("b", 0)]) -/// -pub fn update( - in map: Map(k, v), - update key: k, - with fun: fn(Result(v, Nil)) -> v, -) -> Map(k, v) { - map - |> get(key) - |> fun - |> insert(map, key, _) -} + /// Creates a new map with one entry updated using a given function. + /// + /// If there was not an entry in the map for the given key then the function + /// gets `Error(Nil)` as its argument, otherwise it gets `Ok(value)`. + /// + /// ## Example + /// + /// > let increment = fn(x) { + /// > case x { + /// > Ok(i) -> i + 1 + /// > Error(Nil) -> 0 + /// > } + /// > } + /// > let map = from_list([#("a", 0)]) + /// > + /// > update(map, "a" increment) + /// from_list([#("a", 1)]) + /// + /// > update(map, "b" increment) + /// from_list([#("a", 0), #("b", 0)]) + /// + pub fn update( + in map: Map(k, v), + update key: k, + with fun: fn(Result(v, Nil)) -> v, + ) -> Map(k, v) { + map + |> get(key) + |> fun + |> insert(map, key, _) + } -fn do_fold(list: List(#(k, v)), initial: acc, fun: fn(k, v, acc) -> acc) -> acc { - case list { - [] -> initial - [#(k, v), ..tail] -> do_fold(tail, fun(k, v, initial), fun) + fn do_fold( + list: List(#(k, v)), + initial: acc, + fun: fn(k, v, acc) -> acc, + ) -> acc { + case list { + [] -> initial + [#(k, v), ..tail] -> do_fold(tail, fun(k, v, initial), fun) + } } -} -/// Combines all entries into a single value by calling a given function on each -/// one. -/// -/// Maps are not ordered so the values are not returned in any specific order. Do -/// not write code that relies on the order entries are used by this function -/// as it may change in later versions of Gleam or Erlang. -/// -/// # Examples -/// -/// > let map = from_list([#("a", 1), #("b", 3), #("c", 9)]) -/// > fold(map, 0, fn(key, value, accumulator) { accumulator + value }) -/// 13 -/// -/// > import gleam/string.{append} -/// > fold(map, "", fn(key, value, accumulator) { append(accumulator, value) }) -/// "abc" -/// -pub fn fold( - over map: Map(k, v), - from initial: acc, - with fun: fn(k, v, acc) -> acc, -) -> acc { - map - |> to_list - |> do_fold(initial, fun) + /// Combines all entries into a single value by calling a given function on each + /// one. + /// + /// Maps are not ordered so the values are not returned in any specific order. Do + /// not write code that relies on the order entries are used by this function + /// as it may change in later versions of Gleam or Erlang. + /// + /// # Examples + /// + /// > let map = from_list([#("a", 1), #("b", 3), #("c", 9)]) + /// > fold(map, 0, fn(key, value, accumulator) { accumulator + value }) + /// 13 + /// + /// > import gleam/string.{append} + /// > fold(map, "", fn(key, value, accumulator) { append(accumulator, value) }) + /// "abc" + /// + pub fn fold( + over map: Map(k, v), + from initial: acc, + with fun: fn(k, v, acc) -> acc, + ) -> acc { + map + |> to_list + |> do_fold(initial, fun) + } } diff --git a/src/gleam/option.gleam b/src/gleam/option.gleam index f72f552..feb6f56 100644 --- a/src/gleam/option.gleam +++ b/src/gleam/option.gleam @@ -1,225 +1,227 @@ -import gleam/list +if erlang { + import gleam/list -/// Option represents a value that may be present or not. Some means the value is -/// present, None means the value is not. -/// -/// This is Gleam's alternative to having a value that could be Null, as is -/// possible in some other languages. -/// -pub type Option(a) { - Some(a) - None -} + /// Option represents a value that may be present or not. Some means the value is + /// present, None means the value is not. + /// + /// This is Gleam's alternative to having a value that could be Null, as is + /// possible in some other languages. + /// + pub type Option(a) { + Some(a) + None + } -/// Combines a list of options into a single option. -/// If all elements in the list are Some then returns a Some holding the list of values. -/// If any element is None then returns None. -/// -/// ## Examples -/// -/// ``` -/// > all([Some(1), Some(2)]) -/// Some([1, 2]) -/// -/// > all([Some(1), None]) -/// None -/// ``` -/// -pub fn all(list: List(Option(a))) -> Option(List(a)) { - list.fold_right( - list, - from: Some([]), - with: fn(item, acc) { - case acc, item { - Some(values), Some(value) -> Some([value, ..values]) - _, _ -> None - } - }, - ) -} + /// Combines a list of options into a single option. + /// If all elements in the list are Some then returns a Some holding the list of values. + /// If any element is None then returns None. + /// + /// ## Examples + /// + /// ``` + /// > all([Some(1), Some(2)]) + /// Some([1, 2]) + /// + /// > all([Some(1), None]) + /// None + /// ``` + /// + pub fn all(list: List(Option(a))) -> Option(List(a)) { + list.fold_right( + list, + from: Some([]), + with: fn(item, acc) { + case acc, item { + Some(values), Some(value) -> Some([value, ..values]) + _, _ -> None + } + }, + ) + } -/// Checks whether the option is a Some value. -/// -/// ## Examples -/// -/// > is_some(Some(1)) -/// True -/// -/// > is_some(None) -/// False -/// -pub fn is_some(option: Option(a)) -> Bool { - option != None -} + /// Checks whether the option is a Some value. + /// + /// ## Examples + /// + /// > is_some(Some(1)) + /// True + /// + /// > is_some(None) + /// False + /// + pub fn is_some(option: Option(a)) -> Bool { + option != None + } -/// Checks whether the option is a None value. -/// -/// ## Examples -/// -/// > is_none(Some(1)) -/// False -/// -/// > is_none(None) -/// True -/// -pub fn is_none(option: Option(a)) -> Bool { - option == None -} + /// Checks whether the option is a None value. + /// + /// ## Examples + /// + /// > is_none(Some(1)) + /// False + /// + /// > is_none(None) + /// True + /// + pub fn is_none(option: Option(a)) -> Bool { + option == None + } -/// Converts an Option type to a Result type -/// -/// ## Examples -/// -/// > to_result(Some(1), "some_error") -/// Ok(1) -/// > to_result(None, "some_error") -/// Error("some_error") -/// -pub fn to_result(option: Option(a), e) -> Result(a, e) { - case option { - Some(a) -> Ok(a) - _ -> Error(e) + /// Converts an Option type to a Result type + /// + /// ## Examples + /// + /// > to_result(Some(1), "some_error") + /// Ok(1) + /// > to_result(None, "some_error") + /// Error("some_error") + /// + pub fn to_result(option: Option(a), e) -> Result(a, e) { + case option { + Some(a) -> Ok(a) + _ -> Error(e) + } } -} -/// Converts a Result type to an Option type -/// -/// ## Examples -/// -/// > from_result(Ok(1)) -/// Some(1) -/// > from_result(Error"some_error")) -/// None -/// -pub fn from_result(result: Result(a, e)) -> Option(a) { - case result { - Ok(a) -> Some(a) - _ -> None + /// Converts a Result type to an Option type + /// + /// ## Examples + /// + /// > from_result(Ok(1)) + /// Some(1) + /// > from_result(Error"some_error")) + /// None + /// + pub fn from_result(result: Result(a, e)) -> Option(a) { + case result { + Ok(a) -> Some(a) + _ -> None + } } -} -/// Extracts the value from an option, returning a default value if there is none. -/// -/// ## Examples -/// -/// > unwrap(Some(1), 0) -/// 1 -/// -/// > unwrap(None, 0) -/// 0 -/// -pub fn unwrap(option: Option(a), or default: a) -> a { - case option { - Some(x) -> x - None -> default + /// Extracts the value from an option, returning a default value if there is none. + /// + /// ## Examples + /// + /// > unwrap(Some(1), 0) + /// 1 + /// + /// > unwrap(None, 0) + /// 0 + /// + pub fn unwrap(option: Option(a), or default: a) -> a { + case option { + Some(x) -> x + None -> default + } } -} -/// Updates a value held within the Some of an Option by calling a given function -/// on it. -/// -/// If the option is a None rather than Some the function is not called and the -/// option stays the same. -/// -/// ## Examples -/// -/// > map(over: Some(1), with: fn(x) { x + 1 }) -/// Some(2) -/// -/// > map(over: None, with: fn(x) { x + 1 }) -/// None -/// -pub fn map(over option: Option(a), with fun: fn(a) -> b) -> Option(b) { - case option { - Some(x) -> Some(fun(x)) - None -> None + /// Updates a value held within the Some of an Option by calling a given function + /// on it. + /// + /// If the option is a None rather than Some the function is not called and the + /// option stays the same. + /// + /// ## Examples + /// + /// > map(over: Some(1), with: fn(x) { x + 1 }) + /// Some(2) + /// + /// > map(over: None, with: fn(x) { x + 1 }) + /// None + /// + pub fn map(over option: Option(a), with fun: fn(a) -> b) -> Option(b) { + case option { + Some(x) -> Some(fun(x)) + None -> None + } } -} -/// Merges a nested Option into a single layer. -/// -/// ## Examples -/// -/// > flatten(Some(Some(1))) -/// Some(1) -/// -/// > flatten(Some(None)) -/// None -/// -/// > flatten(None) -/// None -/// -pub fn flatten(option: Option(Option(a))) -> Option(a) { - case option { - Some(x) -> x - None -> None + /// Merges a nested Option into a single layer. + /// + /// ## Examples + /// + /// > flatten(Some(Some(1))) + /// Some(1) + /// + /// > flatten(Some(None)) + /// None + /// + /// > flatten(None) + /// None + /// + pub fn flatten(option: Option(Option(a))) -> Option(a) { + case option { + Some(x) -> x + None -> None + } } -} -/// Updates a value held within the Some of an Option by calling a given function -/// on it, where the given function also returns an Option. The two Options are -/// then merged together into one Option. -/// -/// If the Option is a None rather than Some the function is not called and the -/// Option stays the same. -/// -/// This function is the equivalent of calling `map` followed by `flatten`, and -/// it is useful for chaining together multiple functions that return Options. -/// -/// ## Examples -/// -/// > then(Some(1), fn(x) { Some(x + 1) }) -/// Some(2) -/// -/// > then(Some(1), fn(x) { Some(#("a", x)) }) -/// Some(#("a", 1)) -/// -/// > then(Some(1), fn(x) { None }) -/// None -/// -/// > then(None, fn(x) { Some(x + 1) }) -/// None -/// -pub fn then(option: Option(a), apply fun: fn(a) -> Option(b)) -> Option(b) { - case option { - Some(x) -> fun(x) - None -> None + /// Updates a value held within the Some of an Option by calling a given function + /// on it, where the given function also returns an Option. The two Options are + /// then merged together into one Option. + /// + /// If the Option is a None rather than Some the function is not called and the + /// Option stays the same. + /// + /// This function is the equivalent of calling `map` followed by `flatten`, and + /// it is useful for chaining together multiple functions that return Options. + /// + /// ## Examples + /// + /// > then(Some(1), fn(x) { Some(x + 1) }) + /// Some(2) + /// + /// > then(Some(1), fn(x) { Some(#("a", x)) }) + /// Some(#("a", 1)) + /// + /// > then(Some(1), fn(x) { None }) + /// None + /// + /// > then(None, fn(x) { Some(x + 1) }) + /// None + /// + pub fn then(option: Option(a), apply fun: fn(a) -> Option(b)) -> Option(b) { + case option { + Some(x) -> fun(x) + None -> None + } } -} -/// Returns the first value if it is Some, otherwise return the second value. -/// -/// ## Examples -/// -/// > or(Some(1), Some(2)) -/// Some(1) -/// -/// > or(Some(1), None) -/// Some(1) -/// -/// > or(None, Some(2)) -/// Some(2) -/// -/// > or(None, None) -/// None -/// -pub fn or(first: Option(a), second: Option(a)) -> Option(a) { - case first { - Some(_) -> first - None -> second + /// Returns the first value if it is Some, otherwise return the second value. + /// + /// ## Examples + /// + /// > or(Some(1), Some(2)) + /// Some(1) + /// + /// > or(Some(1), None) + /// Some(1) + /// + /// > or(None, Some(2)) + /// Some(2) + /// + /// > or(None, None) + /// None + /// + pub fn or(first: Option(a), second: Option(a)) -> Option(a) { + case first { + Some(_) -> first + None -> second + } } -} -/// Given a list of options -/// Return only the values inside Some -/// -/// ## Examples -/// -/// ``` -/// > values([Some(1), None, Some(3)]) -/// [1, 3] -/// ``` -/// -pub fn values(options: List(Option(a))) -> List(a) { - list.filter_map(options, fn(op) { to_result(op, "") }) + /// Given a list of options + /// Return only the values inside Some + /// + /// ## Examples + /// + /// ``` + /// > values([Some(1), None, Some(3)]) + /// [1, 3] + /// ``` + /// + pub fn values(options: List(Option(a))) -> List(a) { + list.filter_map(options, fn(op) { to_result(op, "") }) + } } diff --git a/src/gleam/order.gleam b/src/gleam/order.gleam index c030db4..ee81a62 100644 --- a/src/gleam/order.gleam +++ b/src/gleam/order.gleam @@ -1,101 +1,103 @@ -/// Represents the result of a single comparison to determine the precise -/// ordering of two values. -/// -pub type Order { - /// Less-than - Lt +if erlang { + /// Represents the result of a single comparison to determine the precise + /// ordering of two values. + /// + pub type Order { + /// Less-than + Lt - /// Equal - Eq + /// Equal + Eq - /// Greater than - Gt -} + /// Greater than + Gt + } -/// Inverts an order, so less-than becomes greater-than and greater-than -/// becomes less-than. -/// -/// ## Examples -/// -/// > reverse(Lt) -/// Gt -/// -/// > reverse(Eq) -/// Eq -/// -/// > reverse(Lt) -/// Gt -/// -pub fn reverse(order: Order) -> Order { - case order { - Lt -> Gt - Eq -> Eq - Gt -> Lt + /// Inverts an order, so less-than becomes greater-than and greater-than + /// becomes less-than. + /// + /// ## Examples + /// + /// > reverse(Lt) + /// Gt + /// + /// > reverse(Eq) + /// Eq + /// + /// > reverse(Lt) + /// Gt + /// + pub fn reverse(order: Order) -> Order { + case order { + Lt -> Gt + Eq -> Eq + Gt -> Lt + } } -} -/// Produces a numeric representation of the order. -/// -/// ## Examples -/// -/// > to_int(Lt) -/// -1 -/// -/// > to_int(Eq) -/// 0 -/// -/// > to_int(Gt) -/// 1 -/// -pub fn to_int(order: Order) -> Int { - case order { - Lt -> -1 - Eq -> 0 - Gt -> 1 + /// Produces a numeric representation of the order. + /// + /// ## Examples + /// + /// > to_int(Lt) + /// -1 + /// + /// > to_int(Eq) + /// 0 + /// + /// > to_int(Gt) + /// 1 + /// + pub fn to_int(order: Order) -> Int { + case order { + Lt -> -1 + Eq -> 0 + Gt -> 1 + } } -} -/// Compares two Order values to one another, producing a new Order. -/// -/// ## Examples -/// -/// > compare(Eq, with: Lt) -/// Gt -/// -pub fn compare(a: Order, with b: Order) -> Order { - case a, b { - x, y if x == y -> Eq - Lt, _ | Eq, Gt -> Lt - _, _ -> Gt + /// Compares two Order values to one another, producing a new Order. + /// + /// ## Examples + /// + /// > compare(Eq, with: Lt) + /// Gt + /// + pub fn compare(a: Order, with b: Order) -> Order { + case a, b { + x, y if x == y -> Eq + Lt, _ | Eq, Gt -> Lt + _, _ -> Gt + } } -} -/// Returns the largest of two orders. -/// -/// ## Examples -/// -/// > max(Eq, Lt) -/// Eq -/// -pub fn max(a: Order, b: Order) -> Order { - case a, b { - Gt, _ -> Gt - Eq, Lt -> Eq - _, _ -> b + /// Returns the largest of two orders. + /// + /// ## Examples + /// + /// > max(Eq, Lt) + /// Eq + /// + pub fn max(a: Order, b: Order) -> Order { + case a, b { + Gt, _ -> Gt + Eq, Lt -> Eq + _, _ -> b + } } -} -/// Returns the smallest of two orders. -/// -/// ## Examples -/// -/// > min(Eq, Lt) -/// Lt -/// -pub fn min(a: Order, b: Order) -> Order { - case a, b { - Lt, _ -> Lt - Eq, Gt -> Eq - _, _ -> b + /// Returns the smallest of two orders. + /// + /// ## Examples + /// + /// > min(Eq, Lt) + /// Lt + /// + pub fn min(a: Order, b: Order) -> Order { + case a, b { + Lt, _ -> Lt + Eq, Gt -> Eq + _, _ -> b + } } } diff --git a/src/gleam/os.gleam b/src/gleam/os.gleam index fcaf724..af67e35 100644 --- a/src/gleam/os.gleam +++ b/src/gleam/os.gleam @@ -1,66 +1,69 @@ //// Function to interact with the host operating system. -import gleam/list -import gleam/map.{Map} -import gleam/string +if erlang { + import gleam/list + import gleam/map.{Map} + import gleam/string -// Internal type for erlang interop. -external type CharList + // Internal type for erlang interop. + external type CharList -external fn os_getenv() -> List(CharList) = - "os" "getenv" + external fn os_getenv() -> List(CharList) = + "os" "getenv" -external fn os_putenv(key: CharList, value: CharList) -> Bool = - "os" "putenv" + external fn os_putenv(key: CharList, value: CharList) -> Bool = + "os" "putenv" -external fn os_unsetenv(key: CharList) -> Bool = - "os" "unsetenv" + external fn os_unsetenv(key: CharList) -> Bool = + "os" "unsetenv" -external fn char_list_to_string(CharList) -> String = - "unicode" "characters_to_binary" + external fn char_list_to_string(CharList) -> String = + "unicode" "characters_to_binary" -external fn string_to_char_list(String) -> CharList = - "unicode" "characters_to_list" + external fn string_to_char_list(String) -> CharList = + "unicode" "characters_to_list" -/// Returns all environment variables set on the system. -pub fn get_env() -> Map(String, String) { - list.map( - os_getenv(), - fn(char_list) { - assert Ok(value) = string.split_once(char_list_to_string(char_list), "=") - value - }, - ) - |> map.from_list() -} + /// Returns all environment variables set on the system. + pub fn get_env() -> Map(String, String) { + list.map( + os_getenv(), + fn(char_list) { + assert Ok(value) = + string.split_once(char_list_to_string(char_list), "=") + value + }, + ) + |> map.from_list() + } -/// Sets an environment variable. -pub fn insert_env(key: String, value: String) -> Nil { - os_putenv(string_to_char_list(key), string_to_char_list(value)) - Nil -} + /// Sets an environment variable. + pub fn insert_env(key: String, value: String) -> Nil { + os_putenv(string_to_char_list(key), string_to_char_list(value)) + Nil + } -/// Deletes an environment variable. -pub fn delete_env(key: String) -> Nil { - os_unsetenv(string_to_char_list(key)) - Nil -} + /// Deletes an environment variable. + pub fn delete_env(key: String) -> Nil { + os_unsetenv(string_to_char_list(key)) + Nil + } -pub type TimeUnit { - Second - Millisecond - Microsecond - Nanosecond -} + pub type TimeUnit { + Second + Millisecond + Microsecond + Nanosecond + } -/// Returns the current OS system time. -/// -/// https://erlang.org/doc/apps/erts/time_correction.html#OS_System_Time -pub external fn system_time(TimeUnit) -> Int = - "os" "system_time" + /// Returns the current OS system time. + /// + /// https://erlang.org/doc/apps/erts/time_correction.html#OS_System_Time + pub external fn system_time(TimeUnit) -> Int = + "os" "system_time" -/// Returns the current OS system time as a tuple of Ints -/// -/// http://erlang.org/doc/man/os.html#timestamp-0 -pub external fn erlang_timestamp() -> #(Int, Int, Int) = - "os" "timestamp" + /// Returns the current OS system time as a tuple of Ints + /// + /// http://erlang.org/doc/man/os.html#timestamp-0 + pub external fn erlang_timestamp() -> #(Int, Int, Int) = + "os" "timestamp" +} diff --git a/src/gleam/pair.gleam b/src/gleam/pair.gleam index 6143ee9..246ce8c 100644 --- a/src/gleam/pair.gleam +++ b/src/gleam/pair.gleam @@ -1,61 +1,63 @@ -/// Returns the first element in a pair. -/// -/// ## Examples -/// -/// > first(#(1, 2)) -/// 1 -/// -pub fn first(pair: #(a, b)) -> a { - let #(a, _) = pair - a -} +if erlang { + /// Returns the first element in a pair. + /// + /// ## Examples + /// + /// > first(#(1, 2)) + /// 1 + /// + pub fn first(pair: #(a, b)) -> a { + let #(a, _) = pair + a + } -/// Returns the second element in a pair. -/// -/// ## Examples -/// -/// > second(#(1, 2)) -/// 2 -/// -pub fn second(pair: #(a, b)) -> b { - let #(_, a) = pair - a -} + /// Returns the second element in a pair. + /// + /// ## Examples + /// + /// > second(#(1, 2)) + /// 2 + /// + pub fn second(pair: #(a, b)) -> b { + let #(_, a) = pair + a + } -/// Returns a new pair with the elements swapped. -/// -/// ## Examples -/// -/// > swap(#(1, 2)) -/// #(2, 1) -/// -pub fn swap(pair: #(a, b)) -> #(b, a) { - let #(a, b) = pair - #(b, a) -} + /// Returns a new pair with the elements swapped. + /// + /// ## Examples + /// + /// > swap(#(1, 2)) + /// #(2, 1) + /// + pub fn swap(pair: #(a, b)) -> #(b, a) { + let #(a, b) = pair + #(b, a) + } -/// Returns a new pair with the first element having had `with` applied to -/// it. -/// -/// ## Examples -/// -/// > #(1, 2) |> map_first(fn(n) { n * 2 }) -/// 2 -/// -pub fn map_first(of pair: #(a, b), with fun: fn(a) -> c) -> #(c, b) { - let #(a, b) = pair - #(fun(a), b) -} + /// Returns a new pair with the first element having had `with` applied to + /// it. + /// + /// ## Examples + /// + /// > #(1, 2) |> map_first(fn(n) { n * 2 }) + /// 2 + /// + pub fn map_first(of pair: #(a, b), with fun: fn(a) -> c) -> #(c, b) { + let #(a, b) = pair + #(fun(a), b) + } -/// Returns a new pair with the second element having had `with` applied to -/// it. -/// -/// ## Examples -/// -/// > #(1, 2) |> map_second(fn(n) { n * 2 }) -/// 4 -/// -pub fn map_second(of pair: #(a, b), with fun: fn(b) -> c) -> #(a, c) { - let #(a, b) = pair - #(a, fun(b)) + /// Returns a new pair with the second element having had `with` applied to + /// it. + /// + /// ## Examples + /// + /// > #(1, 2) |> map_second(fn(n) { n * 2 }) + /// 4 + /// + pub fn map_second(of pair: #(a, b), with fun: fn(b) -> c) -> #(a, c) { + let #(a, b) = pair + #(a, fun(b)) + } } diff --git a/src/gleam/queue.gleam b/src/gleam/queue.gleam index 6b12cfb..80cea16 100644 --- a/src/gleam/queue.gleam +++ b/src/gleam/queue.gleam @@ -1,254 +1,258 @@ -import gleam/list +if erlang { + import gleam/list -/// A queue is an order collection of elements. It is similar to a list, but -/// unlike a list elements can be added to or removed from either the front or -/// the back in a performant fashion. -/// -/// The internal representation may be different for two queues with the same -/// elements in the same order if the queues were constructed in different -/// ways. This is the price paid for a queue's fast access at both the front -/// and the back. -/// -/// Because of unpredictable internal representation the equality operator `==` -/// may return surprising results, and the `is_equal` and `is_logically_equal` -/// functions are the recommended way to test queues for equality. -/// -pub opaque type Queue(element) { - Queue(in: List(element), out: List(element)) -} + /// A queue is an order collection of elements. It is similar to a list, but + /// unlike a list elements can be added to or removed from either the front or + /// the back in a performant fashion. + /// + /// The internal representation may be different for two queues with the same + /// elements in the same order if the queues were constructed in different + /// ways. This is the price paid for a queue's fast access at both the front + /// and the back. + /// + /// Because of unpredictable internal representation the equality operator `==` + /// may return surprising results, and the `is_equal` and `is_logically_equal` + /// functions are the recommended way to test queues for equality. + /// + pub opaque type Queue(element) { + Queue(in: List(element), out: List(element)) + } -/// Creates a fresh queue that contains no values. -/// -pub fn new() -> Queue(a) { - Queue(in: [], out: []) -} + /// Creates a fresh queue that contains no values. + /// + pub fn new() -> Queue(a) { + Queue(in: [], out: []) + } -/// Converts a list of elements into a queue of the same elements in the same -/// order. The head element in the list becomes the front element in the queue. -/// -/// This function runs in constant time. -/// -/// # Examples -/// -/// > [1, 2, 3] |> from_list |> length -/// 3 -/// -pub fn from_list(list: List(a)) -> Queue(a) { - Queue(in: [], out: list) -} + /// Converts a list of elements into a queue of the same elements in the same + /// order. The head element in the list becomes the front element in the queue. + /// + /// This function runs in constant time. + /// + /// # Examples + /// + /// > [1, 2, 3] |> from_list |> length + /// 3 + /// + pub fn from_list(list: List(a)) -> Queue(a) { + Queue(in: [], out: list) + } -/// Converts a queue of elements into a list of the same elements in the same -/// order. The front element in the queue becomes the head element in the list. -/// -/// This function runs in linear time. -/// -/// # Examples -/// -/// > new() |> push_back(1) |> push_back(2) |> to_list -/// [1, 2] -/// -pub fn to_list(queue: Queue(a)) -> List(a) { - queue.out - |> list.append(list.reverse(queue.in)) -} + /// Converts a queue of elements into a list of the same elements in the same + /// order. The front element in the queue becomes the head element in the list. + /// + /// This function runs in linear time. + /// + /// # Examples + /// + /// > new() |> push_back(1) |> push_back(2) |> to_list + /// [1, 2] + /// + pub fn to_list(queue: Queue(a)) -> List(a) { + queue.out + |> list.append(list.reverse(queue.in)) + } -/// Determines whether or not the queue is empty. -/// -/// This function runs in constant time. -/// -/// ## Examples -/// -/// > [] |> from_list |> is_empty -/// True -/// -/// > [1] |> from_list |> is_empty -/// False -/// -/// > [1, 2] |> from_list |> is_empty -/// False -/// -pub fn is_empty(queue: Queue(a)) -> Bool { - queue.in == [] && queue.out == [] -} + /// Determines whether or not the queue is empty. + /// + /// This function runs in constant time. + /// + /// ## Examples + /// + /// > [] |> from_list |> is_empty + /// True + /// + /// > [1] |> from_list |> is_empty + /// False + /// + /// > [1, 2] |> from_list |> is_empty + /// False + /// + pub fn is_empty(queue: Queue(a)) -> Bool { + queue.in == [] && queue.out == [] + } -/// Counts the number of elements in a given queue. -/// -/// This function has to traverse the queue to determine the number of elements, -/// so it runs in linear time. -/// -/// ## Examples -/// -/// > length(from_list([])) -/// 0 -/// -/// > length(from_list([1])) -/// 1 -/// -/// > length(from_list([1, 2])) -/// 2 -/// -pub fn length(queue: Queue(a)) -> Int { - list.length(queue.in) + list.length(queue.out) -} + /// Counts the number of elements in a given queue. + /// + /// This function has to traverse the queue to determine the number of elements, + /// so it runs in linear time. + /// + /// ## Examples + /// + /// > length(from_list([])) + /// 0 + /// + /// > length(from_list([1])) + /// 1 + /// + /// > length(from_list([1, 2])) + /// 2 + /// + pub fn length(queue: Queue(a)) -> Int { + list.length(queue.in) + list.length(queue.out) + } -/// Pushes an element onto the back of the queue. -/// -/// # Examples -/// -/// > [1, 2] |> from_list |> push_back(3) |> to_list -/// [1, 2, 3] -/// -pub fn push_back(onto queue: Queue(a), this item: a) -> Queue(a) { - Queue(in: [item, ..queue.in], out: queue.out) -} + /// Pushes an element onto the back of the queue. + /// + /// # Examples + /// + /// > [1, 2] |> from_list |> push_back(3) |> to_list + /// [1, 2, 3] + /// + pub fn push_back(onto queue: Queue(a), this item: a) -> Queue(a) { + Queue(in: [item, ..queue.in], out: queue.out) + } -/// Pushes an element onto the front of the queue. -/// -/// # Examples -/// -/// > [0, 0] |> from_list |> push_front(1) |> to_list -/// [1, 0, 0] -/// -pub fn push_front(onto queue: Queue(a), this item: a) -> Queue(a) { - Queue(in: queue.in, out: [item, ..queue.out]) -} + /// Pushes an element onto the front of the queue. + /// + /// # Examples + /// + /// > [0, 0] |> from_list |> push_front(1) |> to_list + /// [1, 0, 0] + /// + pub fn push_front(onto queue: Queue(a), this item: a) -> Queue(a) { + Queue(in: queue.in, out: [item, ..queue.out]) + } -/// Gets the last element from the queue, returning the -/// element and a new queue without that element. -/// -/// This function typically runs in constant time, but will occasionally run in -/// linear time. -/// -/// # Examples -/// -/// > queue.new() -/// > |> queue.push_back(0) -/// > |> queue.push_back(1) -/// > |> queue.pop_back() -/// Ok(#(1, queue.push_front(queue.new(), 0))) -/// -/// > queue.new() -/// > |> queue.push_front(0) -/// > |> queue.pop_back() -/// Ok(#(0, queue.new())) -/// -/// > queue.new() -/// > |> queue.pop_back() -/// Error(Nil) -/// -pub fn pop_back(from queue: Queue(a)) -> Result(#(a, Queue(a)), Nil) { - case queue { - Queue(in: [], out: []) -> Error(Nil) - Queue(in: [], out: out) -> pop_back(Queue(in: list.reverse(out), out: [])) - Queue(in: [first, ..rest], out: out) -> { - let queue = Queue(in: rest, out: out) - Ok(#(first, queue)) + /// Gets the last element from the queue, returning the + /// element and a new queue without that element. + /// + /// This function typically runs in constant time, but will occasionally run in + /// linear time. + /// + /// # Examples + /// + /// > queue.new() + /// > |> queue.push_back(0) + /// > |> queue.push_back(1) + /// > |> queue.pop_back() + /// Ok(#(1, queue.push_front(queue.new(), 0))) + /// + /// > queue.new() + /// > |> queue.push_front(0) + /// > |> queue.pop_back() + /// Ok(#(0, queue.new())) + /// + /// > queue.new() + /// > |> queue.pop_back() + /// Error(Nil) + /// + pub fn pop_back(from queue: Queue(a)) -> Result(#(a, Queue(a)), Nil) { + case queue { + Queue(in: [], out: []) -> Error(Nil) + Queue(in: [], out: out) -> pop_back(Queue(in: list.reverse(out), out: [])) + Queue(in: [first, ..rest], out: out) -> { + let queue = Queue(in: rest, out: out) + Ok(#(first, queue)) + } } } -} -/// Gets the first element from the queue, returning the -/// element and a new queue without that element. -/// -/// This function typically runs in constant time, but will occasionally run in -/// linear time. -/// -/// # Examples -/// -/// > queue.new() -/// > |> queue.push_front(1) -/// > |> queue.push_front(0) -/// > |> queue.pop_front() -/// Ok(#(0, queue.push_back(queue.new(), 1))) -/// -/// > queue.new() -/// > |> queue.push_back(0) -/// > |> queue.pop_front() -/// Ok(#(0, queue.new())) -/// -/// > queue.new() -/// > |> queue.pop_back() -/// Error(Nil) -/// -pub fn pop_front(from queue: Queue(a)) -> Result(#(a, Queue(a)), Nil) { - case queue { - Queue(in: [], out: []) -> Error(Nil) - Queue(in: in, out: []) -> pop_front(Queue(in: [], out: list.reverse(in))) - Queue(in: in, out: [first, ..rest]) -> { - let queue = Queue(in: in, out: rest) - Ok(#(first, queue)) + /// Gets the first element from the queue, returning the + /// element and a new queue without that element. + /// + /// This function typically runs in constant time, but will occasionally run in + /// linear time. + /// + /// # Examples + /// + /// > queue.new() + /// > |> queue.push_front(1) + /// > |> queue.push_front(0) + /// > |> queue.pop_front() + /// Ok(#(0, queue.push_back(queue.new(), 1))) + /// + /// > queue.new() + /// > |> queue.push_back(0) + /// > |> queue.pop_front() + /// Ok(#(0, queue.new())) + /// + /// > queue.new() + /// > |> queue.pop_back() + /// Error(Nil) + /// + pub fn pop_front(from queue: Queue(a)) -> Result(#(a, Queue(a)), Nil) { + case queue { + Queue(in: [], out: []) -> Error(Nil) + Queue(in: in, out: []) -> pop_front(Queue(in: [], out: list.reverse(in))) + Queue(in: in, out: [first, ..rest]) -> { + let queue = Queue(in: in, out: rest) + Ok(#(first, queue)) + } } } -} -/// Creates a new queue from a given queue containing the same elements, but in -/// the opposite order. -/// -/// This function runs in constant time. -/// -/// ## Examples -/// -/// > reverse(from_list([])) -/// [] -/// -/// > reverse(from_list([1])) -/// [1] -/// -/// > reverse(from_list([1, 2])) -/// [2, 1] -/// -pub fn reverse(queue: Queue(a)) -> Queue(a) { - Queue(in: queue.out, out: queue.in) -} + /// Creates a new queue from a given queue containing the same elements, but in + /// the opposite order. + /// + /// This function runs in constant time. + /// + /// ## Examples + /// + /// > reverse(from_list([])) + /// [] + /// + /// > reverse(from_list([1])) + /// [1] + /// + /// > reverse(from_list([1, 2])) + /// [2, 1] + /// + pub fn reverse(queue: Queue(a)) -> Queue(a) { + Queue(in: queue.out, out: queue.in) + } -fn check_equal( - xs: List(t), - x_tail: List(t), - ys: List(t), - y_tail: List(t), - eq: fn(t, t) -> Bool, -) -> Bool { - case xs, x_tail, ys, y_tail { - [], [], [], [] -> True - [x, ..xs], _, [y, ..ys], _ -> - case eq(x, y) { - False -> False - True -> check_equal(xs, x_tail, ys, y_tail, eq) - } - [], [_, .._], _, _ -> check_equal(list.reverse(x_tail), [], ys, y_tail, eq) - _, _, [], [_, .._] -> check_equal(xs, x_tail, list.reverse(y_tail), [], eq) - _, _, _, _ -> False + fn check_equal( + xs: List(t), + x_tail: List(t), + ys: List(t), + y_tail: List(t), + eq: fn(t, t) -> Bool, + ) -> Bool { + case xs, x_tail, ys, y_tail { + [], [], [], [] -> True + [x, ..xs], _, [y, ..ys], _ -> + case eq(x, y) { + False -> False + True -> check_equal(xs, x_tail, ys, y_tail, eq) + } + [], [_, .._], _, _ -> + check_equal(list.reverse(x_tail), [], ys, y_tail, eq) + _, _, [], [_, .._] -> + check_equal(xs, x_tail, list.reverse(y_tail), [], eq) + _, _, _, _ -> False + } } -} -/// Checks whether two queues have equal elements in the same order, where the -/// equality of elements is determined by a given equality checking function. -/// -/// This function is useful as the internal representation may be different for -/// two queues with the same elements in the same order depending on how they -/// were constructed, so the equality operator `==` may return surprising -/// results. -/// -/// This function runs in linear time multiplied by the time taken by the -/// element equality checking function. -/// -pub fn is_logically_equal( - a: Queue(t), - to b: Queue(t), - checking element_is_equal: fn(t, t) -> Bool, -) -> Bool { - check_equal(a.out, a.in, b.out, b.in, element_is_equal) -} + /// Checks whether two queues have equal elements in the same order, where the + /// equality of elements is determined by a given equality checking function. + /// + /// This function is useful as the internal representation may be different for + /// two queues with the same elements in the same order depending on how they + /// were constructed, so the equality operator `==` may return surprising + /// results. + /// + /// This function runs in linear time multiplied by the time taken by the + /// element equality checking function. + /// + pub fn is_logically_equal( + a: Queue(t), + to b: Queue(t), + checking element_is_equal: fn(t, t) -> Bool, + ) -> Bool { + check_equal(a.out, a.in, b.out, b.in, element_is_equal) + } -/// Checks whether two queues have the same elements in the same order. -/// -/// This function is useful as the internal representation may be different for -/// two queues with the same elements in the same order depending on how they -/// were constructed, so the equality operator `==` may return surprising -/// results. -/// -/// This function runs in linear time. -/// -pub fn is_equal(a: Queue(t), to b: Queue(t)) -> Bool { - check_equal(a.out, a.in, b.out, b.in, fn(a, b) { a == b }) + /// Checks whether two queues have the same elements in the same order. + /// + /// This function is useful as the internal representation may be different for + /// two queues with the same elements in the same order depending on how they + /// were constructed, so the equality operator `==` may return surprising + /// results. + /// + /// This function runs in linear time. + /// + pub fn is_equal(a: Queue(t), to b: Queue(t)) -> Bool { + check_equal(a.out, a.in, b.out, b.in, fn(a, b) { a == b }) + } } diff --git a/src/gleam/regex.gleam b/src/gleam/regex.gleam index 07aa1ba..87f06c8 100644 --- a/src/gleam/regex.gleam +++ b/src/gleam/regex.gleam @@ -3,121 +3,123 @@ //// all of the PCRE library is interfaced and some parts of the library go beyond //// what PCRE offers. Currently PCRE version 8.40 (release date 2017-01-11) is used. -import gleam/option.{Option} +if erlang { + import gleam/option.{Option} -pub external type Regex + pub external type Regex -/// The details about a particular match: -/// -pub type Match { - Match( - /// The full string of the match. - content: String, - /// The byte index of the match in the original string. - byte_index: Int, - /// A Regex can have subpatterns, sup-parts that are in parentheses. - submatches: List(Option(String)), - ) -} + /// The details about a particular match: + /// + pub type Match { + Match( + /// The full string of the match. + content: String, + /// The byte index of the match in the original string. + byte_index: Int, + /// A Regex can have subpatterns, sup-parts that are in parentheses. + submatches: List(Option(String)), + ) + } -/// When a regular expression fails to compile: -/// -pub type CompileError { - CompileError( - /// The problem encountered that caused the compilation to fail - error: String, - /// The byte index into the string to where the problem was found - byte_index: Int, - ) -} + /// When a regular expression fails to compile: + /// + pub type CompileError { + CompileError( + /// The problem encountered that caused the compilation to fail + error: String, + /// The byte index into the string to where the problem was found + byte_index: Int, + ) + } -pub type Options { - Options(case_insensitive: Bool, multi_line: Bool) -} + pub type Options { + Options(case_insensitive: Bool, multi_line: Bool) + } -/// Creates a Regex with some additional options. -/// -/// ## Examples -/// -/// > let options = Options(case_insensitive: False, multi_line: True) -/// > assert Ok(re) = compile("^[0-9]", with: options) -/// > match(re, "abc\n123") -/// True -/// -/// > let options = Options(case_insensitive: True, multi_line: False) -/// > assert Ok(re) = compile("[A-Z]", with: options) -/// > match(re, "abc123") -/// True -/// -pub external fn compile(String, with: Options) -> Result(Regex, CompileError) = - "gleam_stdlib" "compile_regex" + /// Creates a Regex with some additional options. + /// + /// ## Examples + /// + /// > let options = Options(case_insensitive: False, multi_line: True) + /// > assert Ok(re) = compile("^[0-9]", with: options) + /// > match(re, "abc\n123") + /// True + /// + /// > let options = Options(case_insensitive: True, multi_line: False) + /// > assert Ok(re) = compile("[A-Z]", with: options) + /// > match(re, "abc123") + /// True + /// + pub external fn compile(String, with: Options) -> Result(Regex, CompileError) = + "gleam_stdlib" "compile_regex" -/// Creates a new Regex. -/// -/// ## Examples -/// -/// > assert Ok(re) = from_string("[0-9]") -/// > match(re, "abc123") -/// True -/// -/// > match(re, "abcxyz") -/// False -/// -/// > from_string("[0-9") -/// Error( -/// CompileError( -/// error: "missing terminating ] for character class", -/// byte_index: 4 -/// ) -/// ) -/// -pub fn from_string(pattern: String) -> Result(Regex, CompileError) { - compile(pattern, Options(case_insensitive: False, multi_line: False)) -} + /// Creates a new Regex. + /// + /// ## Examples + /// + /// > assert Ok(re) = from_string("[0-9]") + /// > match(re, "abc123") + /// True + /// + /// > match(re, "abcxyz") + /// False + /// + /// > from_string("[0-9") + /// Error( + /// CompileError( + /// error: "missing terminating ] for character class", + /// byte_index: 4 + /// ) + /// ) + /// + pub fn from_string(pattern: String) -> Result(Regex, CompileError) { + compile(pattern, Options(case_insensitive: False, multi_line: False)) + } -/// Returns a boolean indicating whether there was a match or not. -/// -/// ## Examples -/// -/// > assert Ok(re) = from_string("^f.o.?") -/// > check(with: re, content: "foo") -/// True -/// -/// > check(with: re, content: "boo") -/// False -/// -pub external fn check(with: Regex, content: String) -> Bool = - "gleam_stdlib" "regex_match" + /// Returns a boolean indicating whether there was a match or not. + /// + /// ## Examples + /// + /// > assert Ok(re) = from_string("^f.o.?") + /// > check(with: re, content: "foo") + /// True + /// + /// > check(with: re, content: "boo") + /// False + /// + pub external fn check(with: Regex, content: String) -> Bool = + "gleam_stdlib" "regex_match" -/// Splits a string -/// -/// ## Examples -/// -/// > assert Ok(re) = from_string(" *, *") -/// > split(with: re, content: "foo,32, 4, 9 ,0") -/// ["foo", "32", "4", "9", "0"] -/// -pub external fn split(with: Regex, content: String) -> List(String) = - "gleam_stdlib" "regex_split" + /// Splits a string + /// + /// ## Examples + /// + /// > assert Ok(re) = from_string(" *, *") + /// > split(with: re, content: "foo,32, 4, 9 ,0") + /// ["foo", "32", "4", "9", "0"] + /// + pub external fn split(with: Regex, content: String) -> List(String) = + "gleam_stdlib" "regex_split" -/// Collects all matches of the regular expression. -/// -/// ## Examples -/// -/// > assert Ok(re) = regex.from_string("[oi]n a (\\w+)") -/// > regex.scan(with: re, content: "I am on a boat in a lake.") -/// [ -/// Match( -/// content: "on a boat", -/// byte_index: 5, -/// submatches: [Some("boat")] -/// ), -/// Match( -/// content: "in a lake", -/// byte_index: 15, -/// submatches: [Some("lake")] -/// ) -/// ] -/// -pub external fn scan(with: Regex, content: String) -> List(Match) = - "gleam_stdlib" "regex_scan" + /// Collects all matches of the regular expression. + /// + /// ## Examples + /// + /// > assert Ok(re) = regex.from_string("[oi]n a (\\w+)") + /// > regex.scan(with: re, content: "I am on a boat in a lake.") + /// [ + /// Match( + /// content: "on a boat", + /// byte_index: 5, + /// submatches: [Some("boat")] + /// ), + /// Match( + /// content: "in a lake", + /// byte_index: 15, + /// submatches: [Some("lake")] + /// ) + /// ] + /// + pub external fn scan(with: Regex, content: String) -> List(Match) = + "gleam_stdlib" "regex_scan" +} diff --git a/src/gleam/result.gleam b/src/gleam/result.gleam index d0601bf..4527516 100644 --- a/src/gleam/result.gleam +++ b/src/gleam/result.gleam @@ -1,280 +1,282 @@ -import gleam/list +if erlang { + import gleam/list -/// Result represents the result of something that may succeed or not. -/// `Ok` means it was successful, `Error` means it was not successful. -/// -pub type Result(success, error) = - Result(success, error) + /// Result represents the result of something that may succeed or not. + /// `Ok` means it was successful, `Error` means it was not successful. + /// + pub type Result(success, error) = + Result(success, error) -/// Nil is a type used to represent the absence of something, similar to null -/// or undefined in other languages. -/// -/// Unlike some other languages values cannot be implicitly nil. -/// -pub type Nil = - Nil + /// Nil is a type used to represent the absence of something, similar to null + /// or undefined in other languages. + /// + /// Unlike some other languages values cannot be implicitly nil. + /// + pub type Nil = + Nil -/// Checks whether the result is an Ok value. -/// -/// ## Examples -/// -/// > is_ok(Ok(1)) -/// True -/// -/// > is_ok(Error(Nil)) -/// False -/// -pub fn is_ok(result: Result(a, e)) -> Bool { - case result { - Error(_) -> False - Ok(_) -> True + /// Checks whether the result is an Ok value. + /// + /// ## Examples + /// + /// > is_ok(Ok(1)) + /// True + /// + /// > is_ok(Error(Nil)) + /// False + /// + pub fn is_ok(result: Result(a, e)) -> Bool { + case result { + Error(_) -> False + Ok(_) -> True + } } -} -/// Checks whether the result is an Error value. -/// -/// ## Examples -/// -/// > is_error(Ok(1)) -/// False -/// -/// > is_error(Error(Nil)) -/// True -/// -pub fn is_error(result: Result(a, e)) -> Bool { - case result { - Ok(_) -> False - Error(_) -> True + /// Checks whether the result is an Error value. + /// + /// ## Examples + /// + /// > is_error(Ok(1)) + /// False + /// + /// > is_error(Error(Nil)) + /// True + /// + pub fn is_error(result: Result(a, e)) -> Bool { + case result { + Ok(_) -> False + Error(_) -> True + } } -} -/// Updates a value held within the Ok of a result by calling a given function -/// on it. -/// -/// If the result is an Error rather than OK the function is not called and the -/// result stays the same. -/// -/// ## Examples -/// -/// > map(over: Ok(1), with: fn(x) { x + 1 }) -/// Ok(2) -/// -/// > map(over: Error(1), with: fn(x) { x + 1 }) -/// Error(1) -/// -pub fn map(over result: Result(a, e), with fun: fn(a) -> b) -> Result(b, e) { - case result { - Ok(x) -> Ok(fun(x)) - Error(e) -> Error(e) + /// Updates a value held within the Ok of a result by calling a given function + /// on it. + /// + /// If the result is an Error rather than OK the function is not called and the + /// result stays the same. + /// + /// ## Examples + /// + /// > map(over: Ok(1), with: fn(x) { x + 1 }) + /// Ok(2) + /// + /// > map(over: Error(1), with: fn(x) { x + 1 }) + /// Error(1) + /// + pub fn map(over result: Result(a, e), with fun: fn(a) -> b) -> Result(b, e) { + case result { + Ok(x) -> Ok(fun(x)) + Error(e) -> Error(e) + } } -} -/// Updates a value held within the Error of a result by calling a given function -/// on it. -/// -/// If the result is Ok rather than Error the function is not called and the -/// result stays the same. -/// -/// ## Examples -/// -/// > map_error(over: Error(1), with: fn(x) { x + 1 }) -/// Error(2) -/// -/// > map_error(over: Ok(1), with: fn(x) { x + 1 }) -/// Ok(1) -/// -pub fn map_error( - over result: Result(a, e), - with fun: fn(e) -> f, -) -> Result(a, f) { - case result { - Ok(x) -> Ok(x) - Error(error) -> Error(fun(error)) + /// Updates a value held within the Error of a result by calling a given function + /// on it. + /// + /// If the result is Ok rather than Error the function is not called and the + /// result stays the same. + /// + /// ## Examples + /// + /// > map_error(over: Error(1), with: fn(x) { x + 1 }) + /// Error(2) + /// + /// > map_error(over: Ok(1), with: fn(x) { x + 1 }) + /// Ok(1) + /// + pub fn map_error( + over result: Result(a, e), + with fun: fn(e) -> f, + ) -> Result(a, f) { + case result { + Ok(x) -> Ok(x) + Error(error) -> Error(fun(error)) + } } -} -/// Merges a nested Result into a single layer. -/// -/// ## Examples -/// -/// > flatten(Ok(Ok(1))) -/// Ok(1) -/// -/// > flatten(Ok(Error("")) -/// Error("") -/// -/// > flatten(Error(Nil)) -/// Error(Nil) -/// -pub fn flatten(result: Result(Result(a, e), e)) -> Result(a, e) { - case result { - Ok(x) -> x - Error(error) -> Error(error) + /// Merges a nested Result into a single layer. + /// + /// ## Examples + /// + /// > flatten(Ok(Ok(1))) + /// Ok(1) + /// + /// > flatten(Ok(Error("")) + /// Error("") + /// + /// > flatten(Error(Nil)) + /// Error(Nil) + /// + pub fn flatten(result: Result(Result(a, e), e)) -> Result(a, e) { + case result { + Ok(x) -> x + Error(error) -> Error(error) + } } -} -/// Updates a value held within the Ok of a result by calling a given function -/// on it, where the given function also returns a result. The two results are -/// then merged together into one result. -/// -/// If the result is an Error rather than OK the function is not called and the -/// result stays the same. -/// -/// This function is the equivalent of calling `map` followed by `flatten`, and -/// it is useful for chaining together multiple functions that may fail. -/// -/// ## Examples -/// -/// > then(Ok(1), fn(x) { Ok(x + 1) }) -/// Ok(2) -/// -/// > then(Ok(1), fn(x) { Ok(#("a", x)) }) -/// Ok(#("a", 1)) -/// -/// > then(Ok(1), fn(x) { Error("Oh no") }) -/// Error("Oh no") -/// -/// > then(Error(Nil), fn(x) { Ok(x + 1) }) -/// Error(Nil) -/// -pub fn then( - result: Result(a, e), - apply fun: fn(a) -> Result(b, e), -) -> Result(b, e) { - case result { - Ok(x) -> fun(x) - Error(e) -> Error(e) + /// Updates a value held within the Ok of a result by calling a given function + /// on it, where the given function also returns a result. The two results are + /// then merged together into one result. + /// + /// If the result is an Error rather than OK the function is not called and the + /// result stays the same. + /// + /// This function is the equivalent of calling `map` followed by `flatten`, and + /// it is useful for chaining together multiple functions that may fail. + /// + /// ## Examples + /// + /// > then(Ok(1), fn(x) { Ok(x + 1) }) + /// Ok(2) + /// + /// > then(Ok(1), fn(x) { Ok(#("a", x)) }) + /// Ok(#("a", 1)) + /// + /// > then(Ok(1), fn(x) { Error("Oh no") }) + /// Error("Oh no") + /// + /// > then(Error(Nil), fn(x) { Ok(x + 1) }) + /// Error(Nil) + /// + pub fn then( + result: Result(a, e), + apply fun: fn(a) -> Result(b, e), + ) -> Result(b, e) { + case result { + Ok(x) -> fun(x) + Error(e) -> Error(e) + } } -} -/// Extracts the Ok value from a result, returning a default value if the result -/// is an Error. -/// -/// ## Examples -/// -/// > unwrap(Ok(1), 0) -/// 1 -/// -/// > unwrap(Error(""), 0) -/// 0 -/// -pub fn unwrap(result: Result(a, e), or default: a) -> a { - case result { - Ok(v) -> v - Error(_) -> default + /// Extracts the Ok value from a result, returning a default value if the result + /// is an Error. + /// + /// ## Examples + /// + /// > unwrap(Ok(1), 0) + /// 1 + /// + /// > unwrap(Error(""), 0) + /// 0 + /// + pub fn unwrap(result: Result(a, e), or default: a) -> a { + case result { + Ok(v) -> v + Error(_) -> default + } } -} -/// Extracts the Ok value from a result, evaluating the default function if the result -/// is an Error. -/// -/// ## Examples -/// -/// > lazy_unwrap(Ok(1), fn() { 0 }) -/// 1 -/// -/// > lazy_unwrap(Error(""), fn() { 0 }) -/// 0 -/// -pub fn lazy_unwrap(result: Result(a, e), or default: fn() -> a) -> a { - case result { - Ok(v) -> v - Error(_) -> default() + /// Extracts the Ok value from a result, evaluating the default function if the result + /// is an Error. + /// + /// ## Examples + /// + /// > lazy_unwrap(Ok(1), fn() { 0 }) + /// 1 + /// + /// > lazy_unwrap(Error(""), fn() { 0 }) + /// 0 + /// + pub fn lazy_unwrap(result: Result(a, e), or default: fn() -> a) -> a { + case result { + Ok(v) -> v + Error(_) -> default() + } } -} -/// Transforms any error into Error(Nil) -/// -/// ## Examples -/// -/// > nil_error(Error(1)) -/// Error(Nil) -/// -/// > nil_error(Ok(1)) -/// Ok(1) -/// -pub fn nil_error(result: Result(a, e)) -> Result(a, Nil) { - map_error(result, fn(_) { Nil }) -} + /// Transforms any error into Error(Nil) + /// + /// ## Examples + /// + /// > nil_error(Error(1)) + /// Error(Nil) + /// + /// > nil_error(Ok(1)) + /// Ok(1) + /// + pub fn nil_error(result: Result(a, e)) -> Result(a, Nil) { + map_error(result, fn(_) { Nil }) + } -/// Returns the first value if it is Ok, otherwise return the second value. -/// -/// ## Examples -/// -/// > or(Ok(1), Ok(2)) -/// Ok(1) -/// -/// > or(Ok(1), Error("Error 2")) -/// Ok(1) -/// -/// > or(Error("Error 1"), Ok(2)) -/// Ok(2) -/// -/// > or(Error("Error 1"), Error("Error 2")) -/// Error("Error 2") -/// -pub fn or(first: Result(a, e), second: Result(a, e)) -> Result(a, e) { - case first { - Ok(_) -> first - Error(_) -> second + /// Returns the first value if it is Ok, otherwise return the second value. + /// + /// ## Examples + /// + /// > or(Ok(1), Ok(2)) + /// Ok(1) + /// + /// > or(Ok(1), Error("Error 2")) + /// Ok(1) + /// + /// > or(Error("Error 1"), Ok(2)) + /// Ok(2) + /// + /// > or(Error("Error 1"), Error("Error 2")) + /// Error("Error 2") + /// + pub fn or(first: Result(a, e), second: Result(a, e)) -> Result(a, e) { + case first { + Ok(_) -> first + Error(_) -> second + } } -} -/// Returns the first value if it is Ok, otherwise evaluates the given function for a fallback value. -/// -/// ## Examples -/// -/// > or(Ok(1), Ok(2)) -/// Ok(1) -/// -/// > or(Ok(1), Error("Error 2")) -/// Ok(1) -/// -/// > or(Error("Error 1"), Ok(2)) -/// Ok(2) -/// -/// > or(Error("Error 1"), Error("Error 2")) -/// Error("Error 2") -/// -pub fn lazy_or( - first: Result(a, e), - second: fn() -> Result(a, e), -) -> Result(a, e) { - case first { - Ok(_) -> first - Error(_) -> second() + /// Returns the first value if it is Ok, otherwise evaluates the given function for a fallback value. + /// + /// ## Examples + /// + /// > or(Ok(1), Ok(2)) + /// Ok(1) + /// + /// > or(Ok(1), Error("Error 2")) + /// Ok(1) + /// + /// > or(Error("Error 1"), Ok(2)) + /// Ok(2) + /// + /// > or(Error("Error 1"), Error("Error 2")) + /// Error("Error 2") + /// + pub fn lazy_or( + first: Result(a, e), + second: fn() -> Result(a, e), + ) -> Result(a, e) { + case first { + Ok(_) -> first + Error(_) -> second() + } } -} -/// Combines a list of results into a single result. -/// If all elements in the list are Ok then returns an Ok holding the list of values. -/// If any element is Error then returns the first error. -/// -/// ## Examples -/// > all([Ok(1), Ok(2)]) -/// Ok([1, 2]) -/// -/// > all([Ok(1), Error("e")]) -/// Error("e") -pub fn all(results: List(Result(a, e))) -> Result(List(a), e) { - list.try_map(results, fn(x) { x }) -} + /// Combines a list of results into a single result. + /// If all elements in the list are Ok then returns an Ok holding the list of values. + /// If any element is Error then returns the first error. + /// + /// ## Examples + /// > all([Ok(1), Ok(2)]) + /// Ok([1, 2]) + /// + /// > all([Ok(1), Error("e")]) + /// Error("e") + pub fn all(results: List(Result(a, e))) -> Result(List(a), e) { + list.try_map(results, fn(x) { x }) + } -pub fn replace_error(result: Result(a, e1), error: e2) -> Result(a, e2) { - result - |> map_error(fn(_) { error }) -} + pub fn replace_error(result: Result(a, e1), error: e2) -> Result(a, e2) { + result + |> map_error(fn(_) { error }) + } -/// Given a list of results -/// Return only the values inside Ok -/// -/// ## Examples -/// -/// ``` -/// > values([Ok(1), None, Ok(3)]) -/// [1, 3] -/// ``` -/// -pub fn values(results: List(Result(a, e))) -> List(a) { - list.filter_map(results, fn(r) { r }) + /// Given a list of results + /// Return only the values inside Ok + /// + /// ## Examples + /// + /// ``` + /// > values([Ok(1), None, Ok(3)]) + /// [1, 3] + /// ``` + /// + pub fn values(results: List(Result(a, e))) -> List(a) { + list.filter_map(results, fn(r) { r }) + } } diff --git a/src/gleam/set.gleam b/src/gleam/set.gleam index 61bb47a..86c3ccb 100644 --- a/src/gleam/set.gleam +++ b/src/gleam/set.gleam @@ -1,207 +1,215 @@ -import gleam/map.{Map} -import gleam/result -import gleam/list +if erlang { + import gleam/map.{Map} + import gleam/result + import gleam/list -/// A set is a collection of unique members of the same type. -/// -/// It is implemented using the `gleam/map` module, so inserts and lookups have -/// logarithmic time complexity. -/// -pub opaque type Set(member) { - // A list is used as the map value as an empty list has the smallest - // representation in Erlang's binary format - Set(map: Map(member, List(Nil))) -} + /// A set is a collection of unique members of the same type. + /// + /// It is implemented using the `gleam/map` module, so inserts and lookups have + /// logarithmic time complexity. + /// + pub opaque type Set(member) { + // A list is used as the map value as an empty list has the smallest + // representation in Erlang's binary format + Set(map: Map(member, List(Nil))) + } -/// Creates a new empty set. -/// -pub fn new() -> Set(member) { - Set(map.new()) -} + /// Creates a new empty set. + /// + pub fn new() -> Set(member) { + Set(map.new()) + } -/// Gets the number of members in a set. -/// -/// This function runs in constant time. -/// -/// ## Examples -/// -/// > new() |> insert(1) |> insert(2) |> size -/// 2 -/// -pub fn size(set: Set(member)) -> Int { - map.size(set.map) -} + /// Gets the number of members in a set. + /// + /// This function runs in constant time. + /// + /// ## Examples + /// + /// > new() |> insert(1) |> insert(2) |> size + /// 2 + /// + pub fn size(set: Set(member)) -> Int { + map.size(set.map) + } -/// Inserts an member into the set. -/// -/// This function runs in logarithmic time. -/// -/// ## Examples -/// -/// > new() |> insert(1) |> insert(2) |> size -/// 2 -/// -pub fn insert(into set: Set(member), this member: member) -> Set(member) { - Set(map: map.insert(set.map, member, [])) -} + /// Inserts an member into the set. + /// + /// This function runs in logarithmic time. + /// + /// ## Examples + /// + /// > new() |> insert(1) |> insert(2) |> size + /// 2 + /// + pub fn insert(into set: Set(member), this member: member) -> Set(member) { + Set(map: map.insert(set.map, member, [])) + } -/// Checks whether a set contains a given member. -/// -/// This function runs in logarithmic time. -/// -/// ## Examples -/// -/// > new() |> insert(2) |> contains(2) -/// True -/// -/// > new() |> insert(2) |> contains(1) -/// False -/// -pub fn contains(in set: Set(member), this member: member) -> Bool { - set.map - |> map.get(member) - |> result.is_ok -} + /// Checks whether a set contains a given member. + /// + /// This function runs in logarithmic time. + /// + /// ## Examples + /// + /// > new() |> insert(2) |> contains(2) + /// True + /// + /// > new() |> insert(2) |> contains(1) + /// False + /// + pub fn contains(in set: Set(member), this member: member) -> Bool { + set.map + |> map.get(member) + |> result.is_ok + } -/// Removes an member from a set. If the set does not contain the member then -/// the set is returned unchanged. -/// -/// This function runs in logarithmic time. -/// -/// ## Examples -/// -/// > new() |> insert(2) |> delete(2) |> contains(1) -/// False -/// -pub fn delete(from set: Set(member), this member: member) -> Set(member) { - Set(map: map.delete(set.map, member)) -} + /// Removes an member from a set. If the set does not contain the member then + /// the set is returned unchanged. + /// + /// This function runs in logarithmic time. + /// + /// ## Examples + /// + /// > new() |> insert(2) |> delete(2) |> contains(1) + /// False + /// + pub fn delete(from set: Set(member), this member: member) -> Set(member) { + Set(map: map.delete(set.map, member)) + } -/// Converts the set into a list of the contained members. -/// -/// The list has no specific ordering, any unintentional ordering may change in -/// future versions of Gleam or Erlang. -/// -/// This function runs in linear time. -/// -/// ## Examples -/// -/// > new() |> insert(2) |> to_list -/// [2] -/// -pub fn to_list(set: Set(member)) -> List(member) { - map.keys(set.map) -} + /// Converts the set into a list of the contained members. + /// + /// The list has no specific ordering, any unintentional ordering may change in + /// future versions of Gleam or Erlang. + /// + /// This function runs in linear time. + /// + /// ## Examples + /// + /// > new() |> insert(2) |> to_list + /// [2] + /// + pub fn to_list(set: Set(member)) -> List(member) { + map.keys(set.map) + } -/// Creates a new set of the members in a given list. -/// -/// This function runs in loglinear time. -/// -/// ## Examples -/// -/// > import gleam/list -/// > [1, 1, 2, 4, 3, 2] |> from_list |> to_list |> list.sort -/// [1, 3, 3, 4] -/// -pub fn from_list(members: List(member)) -> Set(member) { - let map = - list.fold( - over: members, - from: map.new(), - with: fn(k, m) { map.insert(m, k, []) }, - ) - Set(map) -} + /// Creates a new set of the members in a given list. + /// + /// This function runs in loglinear time. + /// + /// ## Examples + /// + /// > import gleam/list + /// > [1, 1, 2, 4, 3, 2] |> from_list |> to_list |> list.sort + /// [1, 3, 3, 4] + /// + pub fn from_list(members: List(member)) -> Set(member) { + let map = + list.fold( + over: members, + from: map.new(), + with: fn(k, m) { map.insert(m, k, []) }, + ) + Set(map) + } -/// Combines all entries into a single value by calling a given function on each -/// one. -/// -/// Sets are not ordered so the values are not returned in any specific order. -/// Do not write code that relies on the order entries are used by this -/// function as it may change in later versions of Gleam or Erlang. -/// -/// # Examples -/// -/// > from_list([1, 3, 9]) -/// > |> fold(0, fn(member, accumulator) { accumulator + member }) -/// 13 -/// -pub fn fold( - over set: Set(member), - from initial: acc, - with reducer: fn(member, acc) -> acc, -) -> acc { - map.fold(over: set.map, from: initial, with: fn(k, _, a) { reducer(k, a) }) -} + /// Combines all entries into a single value by calling a given function on each + /// one. + /// + /// Sets are not ordered so the values are not returned in any specific order. + /// Do not write code that relies on the order entries are used by this + /// function as it may change in later versions of Gleam or Erlang. + /// + /// # Examples + /// + /// > from_list([1, 3, 9]) + /// > |> fold(0, fn(member, accumulator) { accumulator + member }) + /// 13 + /// + pub fn fold( + over set: Set(member), + from initial: acc, + with reducer: fn(member, acc) -> acc, + ) -> acc { + map.fold(over: set.map, from: initial, with: fn(k, _, a) { reducer(k, a) }) + } -/// Creates a new set from an existing set, minus any members that a given -/// function returns False for. -/// -/// This function runs in loglinear time. -/// -/// ## Examples -/// -/// > import gleam/int -/// > from_list([1, 4, 6, 3, 675, 44, 67]) -/// > |> filter(for: int.is_even) -/// > |> to_list -/// [4, 6, 44] -/// -pub fn filter( - in set: Set(member), - for property: fn(member) -> Bool, -) -> Set(member) { - Set(map.filter(in: set.map, for: fn(m, _) { property(m) })) -} + /// Creates a new set from an existing set, minus any members that a given + /// function returns False for. + /// + /// This function runs in loglinear time. + /// + /// ## Examples + /// + /// > import gleam/int + /// > from_list([1, 4, 6, 3, 675, 44, 67]) + /// > |> filter(for: int.is_even) + /// > |> to_list + /// [4, 6, 44] + /// + pub fn filter( + in set: Set(member), + for property: fn(member) -> Bool, + ) -> Set(member) { + Set(map.filter(in: set.map, for: fn(m, _) { property(m) })) + } -/// Creates a new map from a given map, only including any members which are in -/// a given list. -/// -/// This function runs in loglinear time. -/// -/// ## Examples -/// -/// > from_list([1, 2, 3]) |> take([1, 3, 5]) |> to_list -/// [1, 3] -/// -pub fn take(from set: Set(member), keeping desired: List(member)) -> Set(member) { - Set(map.take(from: set.map, keeping: desired)) -} + /// Creates a new map from a given map, only including any members which are in + /// a given list. + /// + /// This function runs in loglinear time. + /// + /// ## Examples + /// + /// > from_list([1, 2, 3]) |> take([1, 3, 5]) |> to_list + /// [1, 3] + /// + pub fn take( + from set: Set(member), + keeping desired: List(member), + ) -> Set(member) { + Set(map.take(from: set.map, keeping: desired)) + } -fn order(first: Set(member), second: Set(member)) -> #(Set(member), Set(member)) { - case map.size(first.map) > map.size(second.map) { - True -> #(first, second) - False -> #(second, first) + fn order( + first: Set(member), + second: Set(member), + ) -> #(Set(member), Set(member)) { + case map.size(first.map) > map.size(second.map) { + True -> #(first, second) + False -> #(second, first) + } } -} -/// Creates a new set that contains all members of both given sets. -/// -/// This function runs in loglinear time. -/// -/// ## Examples -/// -/// > union(from_list([1, 2]), from_list([2, 3])) |> to_list -/// [1, 2, 3] -/// -pub fn union(of first: Set(member), and second: Set(member)) -> Set(member) { - let #(larger, smaller) = order(first, second) - fold(over: smaller, from: larger, with: fn(m, a) { insert(a, m) }) -} + /// Creates a new set that contains all members of both given sets. + /// + /// This function runs in loglinear time. + /// + /// ## Examples + /// + /// > union(from_list([1, 2]), from_list([2, 3])) |> to_list + /// [1, 2, 3] + /// + pub fn union(of first: Set(member), and second: Set(member)) -> Set(member) { + let #(larger, smaller) = order(first, second) + fold(over: smaller, from: larger, with: fn(m, a) { insert(a, m) }) + } -/// Creates a new set that contains members that are present in both given sets. -/// -/// This function runs in loglinear time. -/// -/// ## Examples -/// -/// > intersection(from_list([1, 2]), from_list([2, 3])) |> to_list -/// [2] -/// -pub fn intersection( - of first: Set(member), - and second: Set(member), -) -> Set(member) { - let #(larger, smaller) = order(first, second) - take(from: larger, keeping: to_list(smaller)) + /// Creates a new set that contains members that are present in both given sets. + /// + /// This function runs in loglinear time. + /// + /// ## Examples + /// + /// > intersection(from_list([1, 2]), from_list([2, 3])) |> to_list + /// [2] + /// + pub fn intersection( + of first: Set(member), + and second: Set(member), + ) -> Set(member) { + let #(larger, smaller) = order(first, second) + take(from: larger, keeping: to_list(smaller)) + } } diff --git a/src/gleam/should.gleam b/src/gleam/should.gleam index 0a4b04b..b7c739b 100644 --- a/src/gleam/should.gleam +++ b/src/gleam/should.gleam @@ -4,32 +4,54 @@ //// More information on running eunit can be found in [the rebar3 //// documentation](https://rebar3.org/docs/testing/eunit/). -// TODO: Move this module into another package so it can be used as a -// dep only in test. -pub external type Expectation +if erlang { + // TODO: Move this module into another package so it can be used as a + // dep only in test. + pub external fn equal(a, a) -> Nil = + "gleam_stdlib" "should_equal" -pub external fn equal(a, a) -> Expectation = - "gleam_stdlib" "should_equal" + pub external fn not_equal(a, a) -> Nil = + "gleam_stdlib" "should_not_equal" -pub external fn not_equal(a, a) -> Expectation = - "gleam_stdlib" "should_not_equal" + pub external fn be_ok(Result(a, b)) -> Nil = + "gleam_stdlib" "should_be_ok" -pub fn be_true(actual: Bool) -> Expectation { + pub external fn be_error(Result(a, b)) -> Nil = + "gleam_stdlib" "should_be_error" +} + +if javascript { + pub fn equal(a, b) { + assert True = a == b + Nil + } + + pub fn not_equal(a, b) { + assert True = a != b + Nil + } + + pub fn be_ok(a) { + assert Ok(_) = a + Nil + } + + pub fn be_error(a) { + assert Error(_) = a + Nil + } +} + +pub fn be_true(actual: Bool) -> Nil { actual |> equal(True) } -pub fn be_false(actual: Bool) -> Expectation { +pub fn be_false(actual: Bool) -> Nil { actual |> equal(False) } -pub external fn be_ok(Result(a, b)) -> Expectation = - "gleam_stdlib" "should_be_ok" - -pub external fn be_error(Result(a, b)) -> Expectation = - "gleam_stdlib" "should_be_error" - -pub fn fail() -> Expectation { +pub fn fail() -> Nil { be_true(False) } diff --git a/src/gleam/string.gleam b/src/gleam/string.gleam index ce7cd64..a4574dc 100644 --- a/src/gleam/string.gleam +++ b/src/gleam/string.gleam @@ -1,476 +1,482 @@ //// Strings in Gleam are UTF-8 binaries. They can be written in your code a //// text surrounded by `"double quotes"`. -import gleam/string_builder -import gleam/dynamic.{Dynamic} -import gleam/iterator -import gleam/list -import gleam/order -import gleam/result - -pub type String = - String - -/// A UtfCodepoint is the integer representation of a valid UTF codepoint -pub type UtfCodepoint = - UtfCodepoint - -/// Determines if a string is empty. -/// -/// ## Examples -/// -/// > is_empty("") -/// True -/// -/// > is_empty("the world") -/// False -/// -pub fn is_empty(str: String) -> Bool { - str == "" -} +if erlang { + import gleam/string_builder + import gleam/dynamic.{Dynamic} + import gleam/iterator + import gleam/list + import gleam/order + import gleam/result + + pub type String = + String + + /// A UtfCodepoint is the integer representation of a valid UTF codepoint + pub type UtfCodepoint = + UtfCodepoint + + /// Determines if a string is empty. + /// + /// ## Examples + /// + /// > is_empty("") + /// True + /// + /// > is_empty("the world") + /// False + /// + pub fn is_empty(str: String) -> Bool { + str == "" + } -/// Gets the number of grapheme clusters in a given string. -/// -/// This function has to iterate across the whole string to count the number of -/// graphemes, so it runs in linear time. -/// -/// ## Examples -/// -/// > length("Gleam") -/// 5 -/// -/// > length("ß↑e̊") -/// 3 -/// -/// > length("") -/// 0 -/// -pub external fn length(String) -> Int = - "string" "length" - -/// -/// Reverses a string. -/// -/// This function has to iterate across the whole string so it runs in linear -/// time. -/// -/// ## Examples -/// -/// > reverse("stressed") -/// "desserts" -/// -pub fn reverse(string: String) -> String { - string - |> string_builder.from_string - |> string_builder.reverse - |> string_builder.to_string -} + /// Gets the number of grapheme clusters in a given string. + /// + /// This function has to iterate across the whole string to count the number of + /// graphemes, so it runs in linear time. + /// + /// ## Examples + /// + /// > length("Gleam") + /// 5 + /// + /// > length("ß↑e̊") + /// 3 + /// + /// > length("") + /// 0 + /// + pub external fn length(String) -> Int = + "string" "length" + + /// + /// Reverses a string. + /// + /// This function has to iterate across the whole string so it runs in linear + /// time. + /// + /// ## Examples + /// + /// > reverse("stressed") + /// "desserts" + /// + pub fn reverse(string: String) -> String { + string + |> string_builder.from_string + |> string_builder.reverse + |> string_builder.to_string + } -/// Creates a new string by replacing all occurrences of a given substring. -/// -/// ## Examples -/// -/// > replace("www.example.com", each: ".", with: "-") -/// "www-example-com" -/// -/// > replace("a,b,c,d,e", each: ",", with: "/") -/// "a/b/c/d/e" -/// -pub fn replace( - in string: String, - each pattern: String, - with substitute: String, -) -> String { - string - |> string_builder.from_string - |> string_builder.replace(each: pattern, with: substitute) - |> string_builder.to_string -} + /// Creates a new string by replacing all occurrences of a given substring. + /// + /// ## Examples + /// + /// > replace("www.example.com", each: ".", with: "-") + /// "www-example-com" + /// + /// > replace("a,b,c,d,e", each: ",", with: "/") + /// "a/b/c/d/e" + /// + pub fn replace( + in string: String, + each pattern: String, + with substitute: String, + ) -> String { + string + |> string_builder.from_string + |> string_builder.replace(each: pattern, with: substitute) + |> string_builder.to_string + } -/// Creates a new string with all the graphemes in the input string converted to -/// lowercase. -/// -/// Useful for case-insensitive comparisons. -/// -/// ## Examples -/// -/// > lowercase("X-FILES") -/// "x-files" -/// -pub external fn lowercase(String) -> String = - "string" "lowercase" - -/// Creates a new string with all the graphemes in the input string converted to -/// uppercase. -/// -/// Useful for case-insensitive comparisons and VIRTUAL YELLING. -/// -/// ## Examples -/// -/// > uppercase("skinner") -/// "SKINNER" -/// -pub external fn uppercase(String) -> String = - "string" "uppercase" - -/// Compares two strings to see which is "larger" by comparing their graphemes. -/// -/// This does not compare the size or length of the given strings. -/// -/// ## Examples -/// -/// > compare("Anthony", "Anthony") -/// order.Eq -/// -/// > compare("A", "B") -/// order.Lt -/// -pub external fn compare(String, String) -> order.Order = - "gleam_stdlib" "compare_strings" - -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) + /// Creates a new string with all the graphemes in the input string converted to + /// lowercase. + /// + /// Useful for case-insensitive comparisons. + /// + /// ## Examples + /// + /// > lowercase("X-FILES") + /// "x-files" + /// + pub external fn lowercase(String) -> String = + "string" "lowercase" + + /// Creates a new string with all the graphemes in the input string converted to + /// uppercase. + /// + /// Useful for case-insensitive comparisons and VIRTUAL YELLING. + /// + /// ## Examples + /// + /// > uppercase("skinner") + /// "SKINNER" + /// + pub external fn uppercase(String) -> String = + "string" "uppercase" + + /// Compares two strings to see which is "larger" by comparing their graphemes. + /// + /// This does not compare the size or length of the given strings. + /// + /// ## Examples + /// + /// > compare("Anthony", "Anthony") + /// order.Eq + /// + /// > compare("A", "B") + /// order.Lt + /// + pub external fn compare(String, String) -> order.Order = + "gleam_stdlib" "compare_strings" + + 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) + } } + False -> erl_slice(string, idx, len) } - False -> erl_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 { - string - |> erl_contains(substring) - |> dynamic.string() - |> result.unwrap(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 { + 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) + /// 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 { - haystack - |> erl_contains(needle) - |> dynamic.atom - |> result.is_error -} + 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 { + 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 = - "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 = - "gleam_stdlib" "string_ends_with" - -/// Creates a list of strings by splitting a given string on a given substring. -/// -/// ## Examples -/// -/// > split("home/gleam/desktop/", on: "/") -/// ["home", "gleam", "desktop", ""] -/// -pub fn split(x: String, on substring: String) -> List(String) { - x - |> string_builder.from_string - |> string_builder.split(on: substring) - |> list.map(with: string_builder.to_string) -} + /// Checks whether the first string starts with the second one. + /// + /// ## Examples + /// + /// > starts_with("theory", "ory") + /// False + /// + pub external fn 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 = + "gleam_stdlib" "string_ends_with" + + /// Creates a list of strings by splitting a given string on a given substring. + /// + /// ## Examples + /// + /// > split("home/gleam/desktop/", on: "/") + /// ["home", "gleam", "desktop", ""] + /// + pub fn split(x: String, on substring: String) -> List(String) { + x + |> string_builder.from_string + |> string_builder.split(on: substring) + |> list.map(with: string_builder.to_string) + } -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( - x: String, - on substring: String, -) -> Result(#(String, String), Nil) { - case erl_split(x, substring) { - [first, rest] -> Ok(#(first, rest)) - _ -> Error(Nil) + 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( + x: String, + on substring: String, + ) -> Result(#(String, String), Nil) { + case erl_split(x, substring) { + [first, rest] -> Ok(#(first, rest)) + _ -> Error(Nil) + } } -} -/// Creates a new string by joining two strings together. -/// -/// This function copies both strings and runs in linear time. If you find -/// yourself joining strings frequently consider using the [string_builder](../string_builder) -/// module as it can append strings much faster! -/// -/// ## Examples -/// -/// > append(to: "butter", suffix: "fly") -/// "butterfly" -/// -pub fn append(to first: String, suffix second: String) -> String { - first - |> string_builder.from_string - |> string_builder.append(second) - |> string_builder.to_string -} + /// Creates a new string by joining two strings together. + /// + /// This function copies both strings and runs in linear time. If you find + /// yourself joining strings frequently consider using the [string_builder](../string_builder) + /// module as it can append strings much faster! + /// + /// ## Examples + /// + /// > append(to: "butter", suffix: "fly") + /// "butterfly" + /// + pub fn append(to first: String, suffix second: String) -> String { + first + |> string_builder.from_string + |> string_builder.append(second) + |> string_builder.to_string + } -/// Creates a new string by joining many strings together. -/// -/// This function copies both strings and runs in linear time. If you find -/// yourself joining strings frequently consider using the [string_builder](../string_builder) -/// module as it can append strings much faster! -/// -/// ## Examples -/// -/// > concat(["never", "the", "less"]) -/// "nevertheless" -/// -pub fn concat(strings: List(String)) -> String { - strings - |> string_builder.from_strings - |> string_builder.to_string -} + /// Creates a new string by joining many strings together. + /// + /// This function copies both strings and runs in linear time. If you find + /// yourself joining strings frequently consider using the [string_builder](../string_builder) + /// module as it can append strings much faster! + /// + /// ## Examples + /// + /// > concat(["never", "the", "less"]) + /// "nevertheless" + /// + pub fn concat(strings: List(String)) -> String { + strings + |> string_builder.from_strings + |> string_builder.to_string + } -/// 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 + } -type Direction { - Leading - Trailing - Both -} + type Direction { + Leading + Trailing + Both + } -external fn erl_pad(String, Int, Direction, String) -> String = - "gleam_stdlib" "string_pad" - -/// Pads a string on the left until it has at least given number of Graphemes. -/// -/// ## Examples -/// -/// > pad_left("121", to: 5, with: ".") -/// "..121" -/// -/// > pad_left("121", to: 3, with: ".") -/// "121" -/// -/// > pad_left("121", to: 2, with: ".") -/// "121" -/// -pub fn pad_left(string: String, to length: Int, with pad_string: String) { - erl_pad(string, length, Leading, pad_string) -} + external fn erl_pad(String, Int, Direction, String) -> String = + "gleam_stdlib" "string_pad" + + /// Pads a string on the left until it has at least given number of Graphemes. + /// + /// ## Examples + /// + /// > pad_left("121", to: 5, with: ".") + /// "..121" + /// + /// > pad_left("121", to: 3, with: ".") + /// "121" + /// + /// > pad_left("121", to: 2, with: ".") + /// "121" + /// + pub fn pad_left(string: String, to length: Int, with pad_string: String) { + erl_pad(string, length, Leading, pad_string) + } -/// Pads a string on the right until it has a given length. -/// -/// ## Examples -/// -/// > pad_right("121", to: 5, with: ".") -/// "121.." -/// -/// > pad_right("121", to: 3, with: ".") -/// "121" -/// -/// > pad_right("121", to: 2, with: ".") -/// "121" -/// -pub fn pad_right(string: String, to length: Int, with pad_string: String) { - erl_pad(string, length, Trailing, pad_string) -} + /// Pads a string on the right until it has a given length. + /// + /// ## Examples + /// + /// > pad_right("121", to: 5, with: ".") + /// "121.." + /// + /// > pad_right("121", to: 3, with: ".") + /// "121" + /// + /// > pad_right("121", to: 2, with: ".") + /// "121" + /// + 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 { - erl_trim(string, Both) -} + 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 { + 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 { - erl_trim(string, Leading) -} + /// Removes whitespace on the left of a String. + /// + /// ## Examples + /// + /// > trim_left(" hats \n") + /// "hats \n" + /// + pub fn 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 { - erl_trim(string, Trailing) -} + /// Removes whitespace on the right of a String. + /// + /// ## Examples + /// + /// > trim_right(" hats \n") + /// " hats" + /// + pub fn 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) = - "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)] - _ -> [] + /// 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) = + "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)] + _ -> [] + } } -} -external fn int_to_utf_codepoint(Int) -> UtfCodepoint = - "gleam_stdlib" "identity" - -/// Converts an integer to a UtfCodepoint -/// -/// Returns an error if the integer does not represent a valid UTF codepoint. -/// -pub fn utf_codepoint(value: Int) -> Result(UtfCodepoint, Nil) { - case value { - i if i > 1114111 -> Error(Nil) - 65534 | 65535 -> Error(Nil) - i if i >= 55296 && i <= 57343 -> Error(Nil) - i -> Ok(int_to_utf_codepoint(i)) + external fn int_to_utf_codepoint(Int) -> UtfCodepoint = + "gleam_stdlib" "identity" + + /// Converts an integer to a UtfCodepoint + /// + /// Returns an error if the integer does not represent a valid UTF codepoint. + /// + pub fn utf_codepoint(value: Int) -> Result(UtfCodepoint, Nil) { + case value { + i if i > 1114111 -> Error(Nil) + 65534 | 65535 -> Error(Nil) + i if i >= 55296 && i <= 57343 -> Error(Nil) + i -> Ok(int_to_utf_codepoint(i)) + } } } diff --git a/src/gleam/string_builder.gleam b/src/gleam/string_builder.gleam index 36970b2..c09d60b 100644 --- a/src/gleam/string_builder.gleam +++ b/src/gleam/string_builder.gleam @@ -1,168 +1,170 @@ -/// StringBuilder is a type used for efficiently building strings. -/// -/// When we append one string to another the strings must be copied to a -/// new location in memory so that they can sit together. This behaviour -/// enables efficient reading of the string but copying can be expensive, -/// especially if we want to join many strings together. -/// -/// StringBuilder is different in that it can be joined together in constant time -/// using minimal memory, and then can be efficiently converted to a string -/// using the `to_string` function. -/// -pub external type StringBuilder - -/// Prepends a String onto the start of some StringBuilder. -/// -/// Runs in constant time. -/// -pub external fn prepend(to: StringBuilder, prefix: String) -> StringBuilder = - "gleam_stdlib" "iodata_prepend" - -/// Appends a String onto the end of some StringBuilder. -/// -/// Runs in constant time. -/// -pub external fn append(to: StringBuilder, suffix: String) -> StringBuilder = - "gleam_stdlib" "iodata_append" - -/// Prepends some StringBuilder onto the start of another. -/// -/// Runs in constant time. -/// -pub external fn prepend_builder( - to: StringBuilder, - prefix: StringBuilder, -) -> StringBuilder = - "gleam_stdlib" "iodata_prepend" - -/// Appends some StringBuilder onto the end of another. -/// -/// Runs in constant time. -/// -pub external fn append_builder( - to: StringBuilder, - suffix: StringBuilder, -) -> StringBuilder = - "gleam_stdlib" "iodata_append" - -/// Converts a list of strings into a builder. -/// -/// Runs in constant time. -/// -pub external fn from_strings(List(String)) -> StringBuilder = - "gleam_stdlib" "identity" - -/// Joins a list of builders into a single builder. -/// -/// Runs in constant time. -/// -pub external fn concat(List(StringBuilder)) -> StringBuilder = - "gleam_stdlib" "identity" - -/// Converts a string into a builder. -/// -/// Runs in constant time. -/// -pub external fn from_string(String) -> StringBuilder = - "gleam_stdlib" "identity" - -/// Turns an `StringBuilder` into a `String` -/// -/// This function is implemented natively by the virtual machine and is highly -/// optimised. -/// -pub external fn to_string(StringBuilder) -> String = - "erlang" "iolist_to_binary" - -/// Returns the size of the StringBuilder in bytes. -/// -pub external fn byte_size(StringBuilder) -> Int = - "erlang" "iolist_size" - -/// Creates a builder containing the textual representation of a given float. -/// -pub external fn from_float(Float) -> StringBuilder = - "io_lib_format" "fwrite_g" - -/// Converts a builder to a new builder where the contents have been -/// lowercased. -/// -pub external fn lowercase(StringBuilder) -> StringBuilder = - "string" "lowercase" - -/// Converts a builder to a new builder where the contents have been -/// uppercased. -/// -pub external fn uppercase(StringBuilder) -> StringBuilder = - "string" "uppercase" - -/// Converts a builder to a new builder with the contents reversed. -/// -pub external fn reverse(StringBuilder) -> StringBuilder = - "string" "reverse" - -type Direction { - All +if erlang { + /// StringBuilder is a type used for efficiently building strings. + /// + /// When we append one string to another the strings must be copied to a + /// new location in memory so that they can sit together. This behaviour + /// enables efficient reading of the string but copying can be expensive, + /// especially if we want to join many strings together. + /// + /// StringBuilder is different in that it can be joined together in constant time + /// using minimal memory, and then can be efficiently converted to a string + /// using the `to_string` function. + /// + pub external type StringBuilder + + /// Prepends a String onto the start of some StringBuilder. + /// + /// Runs in constant time. + /// + pub external fn prepend(to: StringBuilder, prefix: String) -> StringBuilder = + "gleam_stdlib" "iodata_prepend" + + /// Appends a String onto the end of some StringBuilder. + /// + /// Runs in constant time. + /// + pub external fn append(to: StringBuilder, suffix: String) -> StringBuilder = + "gleam_stdlib" "iodata_append" + + /// Prepends some StringBuilder onto the start of another. + /// + /// Runs in constant time. + /// + pub external fn prepend_builder( + to: StringBuilder, + prefix: StringBuilder, + ) -> StringBuilder = + "gleam_stdlib" "iodata_prepend" + + /// Appends some StringBuilder onto the end of another. + /// + /// Runs in constant time. + /// + pub external fn append_builder( + to: StringBuilder, + suffix: StringBuilder, + ) -> StringBuilder = + "gleam_stdlib" "iodata_append" + + /// Converts a list of strings into a builder. + /// + /// Runs in constant time. + /// + pub external fn from_strings(List(String)) -> StringBuilder = + "gleam_stdlib" "identity" + + /// Joins a list of builders into a single builder. + /// + /// Runs in constant time. + /// + pub external fn concat(List(StringBuilder)) -> StringBuilder = + "gleam_stdlib" "identity" + + /// Converts a string into a builder. + /// + /// Runs in constant time. + /// + pub external fn from_string(String) -> StringBuilder = + "gleam_stdlib" "identity" + + /// Turns an `StringBuilder` into a `String` + /// + /// This function is implemented natively by the virtual machine and is highly + /// optimised. + /// + pub external fn to_string(StringBuilder) -> String = + "erlang" "iolist_to_binary" + + /// Returns the size of the StringBuilder in bytes. + /// + pub external fn byte_size(StringBuilder) -> Int = + "erlang" "iolist_size" + + /// Creates a builder containing the textual representation of a given float. + /// + pub external fn from_float(Float) -> StringBuilder = + "io_lib_format" "fwrite_g" + + /// Converts a builder to a new builder where the contents have been + /// lowercased. + /// + pub external fn lowercase(StringBuilder) -> StringBuilder = + "string" "lowercase" + + /// Converts a builder to a new builder where the contents have been + /// uppercased. + /// + pub external fn uppercase(StringBuilder) -> StringBuilder = + "string" "uppercase" + + /// Converts a builder to a new builder with the contents reversed. + /// + pub external fn reverse(StringBuilder) -> StringBuilder = + "string" "reverse" + + type Direction { + All + } + + external fn erl_split(StringBuilder, String, Direction) -> List(StringBuilder) = + "string" "split" + + /// Splits a builder on a given pattern into a list of builders. + /// + pub fn split(iodata: StringBuilder, on pattern: String) -> List(StringBuilder) { + erl_split(iodata, pattern, All) + } + + external fn erl_replace( + StringBuilder, + String, + String, + Direction, + ) -> StringBuilder = + "string" "replace" + + /// Replaces all instances of a pattern with a given string substitute. + /// + pub fn replace( + in iodata: StringBuilder, + each pattern: String, + with substitute: String, + ) -> StringBuilder { + erl_replace(iodata, pattern, substitute, All) + } + + /// Compares two builders to determine if they have the same textual content. + /// + /// Comparing two iodata using the `==` operator may return False even if they + /// have the same content as they may have been build in different ways, so + /// using this function is often preferred. + /// + /// ## Examples + /// + /// > from_strings(["a", "b"]) == new("ab") + /// False + /// + /// > is_equal(from_strings(["a", "b"]), new("ab")) + /// True + /// + /// + pub external fn is_equal(StringBuilder, StringBuilder) -> Bool = + "string" "equal" + + /// Inspects a builder to determine if it is equivalent to an empty string. + /// + /// ## Examples + /// + /// > new("ok") |> is_empty + /// False + /// + /// > new("") |> is_empty + /// True + /// + /// > from_strings([]) |> is_empty + /// True + /// + /// + pub external fn is_empty(StringBuilder) -> Bool = + "string" "is_empty" } - -external fn erl_split(StringBuilder, String, Direction) -> List(StringBuilder) = - "string" "split" - -/// Splits a builder on a given pattern into a list of builders. -/// -pub fn split(iodata: StringBuilder, on pattern: String) -> List(StringBuilder) { - erl_split(iodata, pattern, All) -} - -external fn erl_replace( - StringBuilder, - String, - String, - Direction, -) -> StringBuilder = - "string" "replace" - -/// Replaces all instances of a pattern with a given string substitute. -/// -pub fn replace( - in iodata: StringBuilder, - each pattern: String, - with substitute: String, -) -> StringBuilder { - erl_replace(iodata, pattern, substitute, All) -} - -/// Compares two builders to determine if they have the same textual content. -/// -/// Comparing two iodata using the `==` operator may return False even if they -/// have the same content as they may have been build in different ways, so -/// using this function is often preferred. -/// -/// ## Examples -/// -/// > from_strings(["a", "b"]) == new("ab") -/// False -/// -/// > is_equal(from_strings(["a", "b"]), new("ab")) -/// True -/// -/// -pub external fn is_equal(StringBuilder, StringBuilder) -> Bool = - "string" "equal" - -/// Inspects a builder to determine if it is equivalent to an empty string. -/// -/// ## Examples -/// -/// > new("ok") |> is_empty -/// False -/// -/// > new("") |> is_empty -/// True -/// -/// > from_strings([]) |> is_empty -/// True -/// -/// -pub external fn is_empty(StringBuilder) -> Bool = - "string" "is_empty" diff --git a/src/gleam/uri.gleam b/src/gleam/uri.gleam index a65d753..b40b381 100644 --- a/src/gleam/uri.gleam +++ b/src/gleam/uri.gleam @@ -7,351 +7,353 @@ //// Query encoding (Form encoding) is defined in the w3c specification. //// https://www.w3.org/TR/html52/sec-forms.html#urlencoded-form-data -import gleam/list -import gleam/result -import gleam/option.{None, Option, Some} -import gleam/string -import gleam/dynamic.{Dynamic} -import gleam/map.{Map} -import gleam/function -import gleam/pair +if erlang { + import gleam/list + import gleam/result + import gleam/option.{None, Option, Some} + import gleam/string + import gleam/dynamic.{Dynamic} + import gleam/map.{Map} + import gleam/function + import gleam/pair -/// Type representing holding the parsed components of an URI. -/// All components of a URI are optional, except the path. -/// -pub type Uri { - Uri( - scheme: Option(String), - userinfo: Option(String), - host: Option(String), - port: Option(Int), - path: String, - query: Option(String), - fragment: Option(String), - ) -} - -pub external fn erl_parse(String) -> Dynamic = - "uri_string" "parse" + /// Type representing holding the parsed components of an URI. + /// All components of a URI are optional, except the path. + /// + pub type Uri { + Uri( + scheme: Option(String), + userinfo: Option(String), + host: Option(String), + port: Option(Int), + path: String, + query: Option(String), + fragment: Option(String), + ) + } -type UriKey { - Scheme - Userinfo - Host - Port - Path - Query - Fragment -} + pub external fn erl_parse(String) -> Dynamic = + "uri_string" "parse" -/// Parses a compliant URI string into the `Uri` Type. -/// If the string is not a valid URI string then an error is returned. -/// -/// The opposite operation is `uri.to_string` -/// -/// ## Examples -/// -/// ``` -/// > parse("https://example.com:1234/a/b?query=true#fragment") -/// -/// Ok(Uri(scheme: Some("https"), ...)) -/// ``` -/// -pub fn parse(string: String) -> Result(Uri, Nil) { - try uri_map = - dynamic.map(erl_parse(string)) - |> result.nil_error - let get = fn(k: UriKey, decode_type: dynamic.Decoder(t)) -> Option(t) { - uri_map - |> map.get(dynamic.from(k)) - |> result.then(function.compose(decode_type, result.nil_error)) - |> option.from_result + type UriKey { + Scheme + Userinfo + Host + Port + Path + Query + Fragment } - let uri = - Uri( - scheme: get(Scheme, dynamic.string), - userinfo: get(Userinfo, dynamic.string), - host: get(Host, dynamic.string), - port: get(Port, dynamic.int), - path: option.unwrap(get(Path, dynamic.string), ""), - query: get(Query, dynamic.string), - fragment: get(Fragment, dynamic.string), - ) - Ok(uri) -} + /// Parses a compliant URI string into the `Uri` Type. + /// If the string is not a valid URI string then an error is returned. + /// + /// The opposite operation is `uri.to_string` + /// + /// ## Examples + /// + /// ``` + /// > parse("https://example.com:1234/a/b?query=true#fragment") + /// + /// Ok(Uri(scheme: Some("https"), ...)) + /// ``` + /// + pub fn parse(string: String) -> Result(Uri, Nil) { + try uri_map = + dynamic.map(erl_parse(string)) + |> result.nil_error + let get = fn(k: UriKey, decode_type: dynamic.Decoder(t)) -> Option(t) { + uri_map + |> map.get(dynamic.from(k)) + |> result.then(function.compose(decode_type, result.nil_error)) + |> option.from_result + } -external fn erl_parse_query(String) -> Dynamic = - "uri_string" "dissect_query" + let uri = + Uri( + scheme: get(Scheme, dynamic.string), + userinfo: get(Userinfo, dynamic.string), + host: get(Host, dynamic.string), + port: get(Port, dynamic.int), + path: option.unwrap(get(Path, dynamic.string), ""), + query: get(Query, dynamic.string), + fragment: get(Fragment, dynamic.string), + ) + Ok(uri) + } -/// Parses an urlencoded query string into a list of key value pairs. -/// Returns an error for invalid encoding. -/// -/// The opposite operation is `uri.query_to_string`. -/// -/// ## Examples -/// -/// ``` -/// > parse_query("a=1&b=2") -/// -/// Ok([#("a", "1"), #("b", "2")]) -/// ``` -/// -pub fn parse_query(query: String) -> Result(List(#(String, String)), Nil) { - let bool_value = fn(x) { result.map(dynamic.bool(x), fn(_) { "" }) } - let query_param = dynamic.typed_tuple2( - _, - dynamic.string, - dynamic.any(_, of: [dynamic.string, bool_value]), - ) + external fn erl_parse_query(String) -> Dynamic = + "uri_string" "dissect_query" - query - |> erl_parse_query - |> dynamic.typed_list(of: query_param) - |> result.nil_error -} + /// Parses an urlencoded query string into a list of key value pairs. + /// Returns an error for invalid encoding. + /// + /// The opposite operation is `uri.query_to_string`. + /// + /// ## Examples + /// + /// ``` + /// > parse_query("a=1&b=2") + /// + /// Ok([#("a", "1"), #("b", "2")]) + /// ``` + /// + pub fn parse_query(query: String) -> Result(List(#(String, String)), Nil) { + let bool_value = fn(x) { result.map(dynamic.bool(x), fn(_) { "" }) } + let query_param = dynamic.typed_tuple2( + _, + dynamic.string, + dynamic.any(_, of: [dynamic.string, bool_value]), + ) -type Encoding { - Utf8 -} + query + |> erl_parse_query + |> dynamic.typed_list(of: query_param) + |> result.nil_error + } -type ErlQueryToStringOption { - Encoding(Encoding) -} + type Encoding { + Utf8 + } -external fn erl_query_to_string( - List(#(String, String)), - List(ErlQueryToStringOption), -) -> Dynamic = - "uri_string" "compose_query" + type ErlQueryToStringOption { + Encoding(Encoding) + } -/// Encodes a list of key value pairs as a URI query string. -/// -/// The opposite operation is `uri.parse_query`. -/// -/// ## Examples -/// -/// ``` -/// > query_to_string([#("a", "1"), #("b", "2")]) -/// -/// "a=1&b=2" -/// ``` -/// -pub fn query_to_string(query: List(#(String, String))) -> String { - query - |> erl_query_to_string([Encoding(Utf8)]) - |> dynamic.string - |> result.unwrap("") -} + external fn erl_query_to_string( + List(#(String, String)), + List(ErlQueryToStringOption), + ) -> Dynamic = + "uri_string" "compose_query" -/// Encodes a string into a percent encoded representation. -/// Note that this encodes space as +. -/// -/// ## Examples -/// -/// ``` -/// > percent_encode("100% great") -/// -/// "100%25+great" -/// ``` -/// -pub fn percent_encode(value: String) -> String { - query_to_string([#("k", value)]) - |> string.replace(each: "k=", with: "") -} + /// Encodes a list of key value pairs as a URI query string. + /// + /// The opposite operation is `uri.parse_query`. + /// + /// ## Examples + /// + /// ``` + /// > query_to_string([#("a", "1"), #("b", "2")]) + /// + /// "a=1&b=2" + /// ``` + /// + pub fn query_to_string(query: List(#(String, String))) -> String { + query + |> erl_query_to_string([Encoding(Utf8)]) + |> dynamic.string + |> result.unwrap("") + } -/// Decodes a percent encoded string. -/// -/// ## Examples -/// -/// ``` -/// > percent_decode("100%25+great") -/// -/// Ok("100% great") -/// ``` -/// -pub fn percent_decode(value: String) -> Result(String, Nil) { - string.concat(["k=", value]) - |> parse_query - |> result.then(list.head) - |> result.map(pair.second) -} + /// Encodes a string into a percent encoded representation. + /// Note that this encodes space as +. + /// + /// ## Examples + /// + /// ``` + /// > percent_encode("100% great") + /// + /// "100%25+great" + /// ``` + /// + pub fn percent_encode(value: String) -> String { + query_to_string([#("k", value)]) + |> string.replace(each: "k=", with: "") + } + + /// Decodes a percent encoded string. + /// + /// ## Examples + /// + /// ``` + /// > percent_decode("100%25+great") + /// + /// Ok("100% great") + /// ``` + /// + pub fn percent_decode(value: String) -> Result(String, Nil) { + string.concat(["k=", value]) + |> parse_query + |> result.then(list.head) + |> result.map(pair.second) + } -fn do_remove_dot_segments( - input: List(String), - accumulator: List(String), -) -> List(String) { - case input { - [] -> list.reverse(accumulator) - [segment, ..rest] -> { - let accumulator = case segment, accumulator { - "", accumulator -> accumulator - ".", accumulator -> accumulator - "..", [] -> [] - "..", [_, ..accumulator] -> accumulator - segment, accumulator -> [segment, ..accumulator] + fn do_remove_dot_segments( + input: List(String), + accumulator: List(String), + ) -> List(String) { + case input { + [] -> list.reverse(accumulator) + [segment, ..rest] -> { + let accumulator = case segment, accumulator { + "", accumulator -> accumulator + ".", accumulator -> accumulator + "..", [] -> [] + "..", [_, ..accumulator] -> accumulator + segment, accumulator -> [segment, ..accumulator] + } + do_remove_dot_segments(rest, accumulator) } - do_remove_dot_segments(rest, accumulator) } } -} -fn remove_dot_segments(input: List(String)) -> List(String) { - do_remove_dot_segments(input, []) -} + fn remove_dot_segments(input: List(String)) -> List(String) { + do_remove_dot_segments(input, []) + } -/// Splits the path section of a URI into it's constituent segments. -/// -/// Removes empty segments and resolves dot-segments as specified in -/// [section 5.2](https://www.ietf.org/rfc/rfc3986.html#section-5.2) of the RFC. -/// -/// ## Examples -/// -/// ``` -/// > path_segments("/users/1") -/// -/// ["users" ,"1"] -/// ``` -/// -pub fn path_segments(path: String) -> List(String) { - remove_dot_segments(string.split(path, "/")) -} + /// Splits the path section of a URI into it's constituent segments. + /// + /// Removes empty segments and resolves dot-segments as specified in + /// [section 5.2](https://www.ietf.org/rfc/rfc3986.html#section-5.2) of the RFC. + /// + /// ## Examples + /// + /// ``` + /// > path_segments("/users/1") + /// + /// ["users" ,"1"] + /// ``` + /// + pub fn path_segments(path: String) -> List(String) { + remove_dot_segments(string.split(path, "/")) + } -external fn erl_to_string(Map(UriKey, Dynamic)) -> Dynamic = - "uri_string" "recompose" + external fn erl_to_string(Map(UriKey, Dynamic)) -> Dynamic = + "uri_string" "recompose" -/// Encodes a `Uri` value as a URI string. -/// -/// The opposite operation is `uri.parse`. -/// -/// ## Examples -/// -/// ``` -/// > let uri = Uri(Some("http"), None, Some("example.com"), ...) -/// > to_string(uri) -/// -/// "https://example.com" -/// ``` -/// -pub fn to_string(uri: Uri) -> String { - let field = fn(key: UriKey, value: Option(anything)) -> Result( - #(UriKey, Dynamic), - Nil, - ) { - case value { - Some(v) -> Ok(#(key, dynamic.from(v))) - None -> Error(Nil) + /// Encodes a `Uri` value as a URI string. + /// + /// The opposite operation is `uri.parse`. + /// + /// ## Examples + /// + /// ``` + /// > let uri = Uri(Some("http"), None, Some("example.com"), ...) + /// > to_string(uri) + /// + /// "https://example.com" + /// ``` + /// + pub fn to_string(uri: Uri) -> String { + let field = fn(key: UriKey, value: Option(anything)) -> Result( + #(UriKey, Dynamic), + Nil, + ) { + case value { + Some(v) -> Ok(#(key, dynamic.from(v))) + None -> Error(Nil) + } } - } - [ - field(Scheme, uri.scheme), - field(Userinfo, uri.userinfo), - field(Host, uri.host), - field(Port, uri.port), - field(Path, option.Some(uri.path)), - field(Query, uri.query), - field(Fragment, uri.fragment), - ] - |> list.filter_map(fn(x) { x }) - |> map.from_list - |> erl_to_string - |> dynamic.string - |> result.unwrap("") -} + [ + field(Scheme, uri.scheme), + field(Userinfo, uri.userinfo), + field(Host, uri.host), + field(Port, uri.port), + field(Path, option.Some(uri.path)), + field(Query, uri.query), + field(Fragment, uri.fragment), + ] + |> list.filter_map(fn(x) { x }) + |> map.from_list + |> erl_to_string + |> dynamic.string + |> result.unwrap("") + } -/// Fetches the origin of a uri -/// -/// Return the origin of a uri as defined in -/// https://tools.ietf.org/html/rfc6454 -/// -/// The supported uri schemes are `http` and `https` -/// Urls without a scheme will return Error -/// -/// ## Examples -/// -/// ``` -/// > assert Ok(uri) = parse("http://example.com/path?foo#bar") -/// > origin(uri) -/// -/// Ok("http://example.com") -/// ``` -/// -pub fn origin(uri: Uri) -> Result(String, Nil) { - let Uri(scheme: scheme, host: host, port: port, ..) = uri - case scheme { - Some("https") | Some("http") -> { - let origin = Uri(scheme, None, host, port, "", None, None) - Ok(to_string(origin)) + /// Fetches the origin of a uri + /// + /// Return the origin of a uri as defined in + /// https://tools.ietf.org/html/rfc6454 + /// + /// The supported uri schemes are `http` and `https` + /// Urls without a scheme will return Error + /// + /// ## Examples + /// + /// ``` + /// > assert Ok(uri) = parse("http://example.com/path?foo#bar") + /// > origin(uri) + /// + /// Ok("http://example.com") + /// ``` + /// + pub fn origin(uri: Uri) -> Result(String, Nil) { + let Uri(scheme: scheme, host: host, port: port, ..) = uri + case scheme { + Some("https") | Some("http") -> { + let origin = Uri(scheme, None, host, port, "", None, None) + Ok(to_string(origin)) + } + _ -> Error(Nil) } - _ -> Error(Nil) } -} -fn drop_last(elements: List(a)) -> List(a) { - list.take(from: elements, up_to: list.length(elements) - 1) -} + fn drop_last(elements: List(a)) -> List(a) { + list.take(from: elements, up_to: list.length(elements) - 1) + } -fn join_segments(segments: List(String)) -> String { - string.join(["", ..segments], "/") -} + fn join_segments(segments: List(String)) -> String { + string.join(["", ..segments], "/") + } -/// Resolves a uri with respect to the given base uri -/// -/// The base uri must be an absolute uri or this function will return an error. -/// The algorithm for merging uris is described in [RFC 3986](https://tools.ietf.org/html/rfc3986#section-5.2) -pub fn merge(base: Uri, relative: Uri) -> Result(Uri, Nil) { - case base { - Uri(scheme: Some(_), host: Some(_), ..) -> - case relative { - Uri(host: Some(_), ..) -> { - let path = - string.split(relative.path, "/") - |> remove_dot_segments() - |> join_segments() - let resolved = - Uri( - option.or(relative.scheme, base.scheme), - None, - relative.host, - relative.port, - path, - relative.query, - relative.fragment, - ) - Ok(resolved) - } - Uri(scheme: None, host: None, ..) -> { - let #(new_path, new_query) = case relative.path { - "" -> #(base.path, option.or(relative.query, base.query)) - _ -> { - let path_segments = case string.starts_with(relative.path, "/") { - True -> string.split(relative.path, "/") - False -> - string.split(base.path, "/") - |> drop_last() - |> list.append(string.split(relative.path, "/")) + /// Resolves a uri with respect to the given base uri + /// + /// The base uri must be an absolute uri or this function will return an error. + /// The algorithm for merging uris is described in [RFC 3986](https://tools.ietf.org/html/rfc3986#section-5.2) + pub fn merge(base: Uri, relative: Uri) -> Result(Uri, Nil) { + case base { + Uri(scheme: Some(_), host: Some(_), ..) -> + case relative { + Uri(host: Some(_), ..) -> { + let path = + string.split(relative.path, "/") + |> remove_dot_segments() + |> join_segments() + let resolved = + Uri( + option.or(relative.scheme, base.scheme), + None, + relative.host, + relative.port, + path, + relative.query, + relative.fragment, + ) + Ok(resolved) + } + Uri(scheme: None, host: None, ..) -> { + let #(new_path, new_query) = case relative.path { + "" -> #(base.path, option.or(relative.query, base.query)) + _ -> { + let path_segments = case string.starts_with(relative.path, "/") { + True -> string.split(relative.path, "/") + False -> + string.split(base.path, "/") + |> drop_last() + |> list.append(string.split(relative.path, "/")) + } + let path = + path_segments + |> remove_dot_segments() + |> join_segments() + #(path, relative.query) } - let path = - path_segments - |> remove_dot_segments() - |> join_segments() - #(path, relative.query) } + let resolved = + Uri( + base.scheme, + None, + base.host, + base.port, + new_path, + new_query, + relative.fragment, + ) + Ok(resolved) } - let resolved = - Uri( - base.scheme, - None, - base.host, - base.port, - new_path, - new_query, - relative.fragment, - ) - Ok(resolved) } - } - _ -> Error(Nil) + _ -> Error(Nil) + } } } diff --git a/src/gleam_stdlib.erl b/src/gleam_stdlib.erl index 10d3752..fe70eea 100644 --- a/src/gleam_stdlib.erl +++ b/src/gleam_stdlib.erl @@ -14,10 +14,18 @@ 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]). -should_equal(Actual, Expected) -> ?assertEqual(Expected, Actual). -should_not_equal(Actual, Expected) -> ?assertNotEqual(Expected, Actual). -should_be_ok(A) -> ?assertMatch({ok, _}, A). -should_be_error(A) -> ?assertMatch({error, _}, A). +should_equal(Actual, Expected) -> + ?assertEqual(Expected, Actual), + nil. +should_not_equal(Actual, Expected) -> + ?assertNotEqual(Expected, Actual), + nil. +should_be_ok(A) -> + ?assertMatch({ok, _}, A), + nil. +should_be_error(A) -> + ?assertMatch({error, _}, A), + nil. map_get(Map, Key) -> case maps:find(Key, Map) of diff --git a/src/gleam_stdlib.js b/src/gleam_stdlib.js new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/src/gleam_stdlib.js diff --git a/test/gleam/atom_test.gleam b/test/gleam/atom_test.gleam index 228791d..ccc34ed 100644 --- a/test/gleam/atom_test.gleam +++ b/test/gleam/atom_test.gleam @@ -1,45 +1,47 @@ -import gleam/atom -import gleam/should - -pub fn from_string_test() { - atom.create_from_string("this is an existing atom") - - "this is an existing atom" - |> atom.from_string - |> should.be_ok - - "this is not an atom we have seen before" - |> atom.from_string - |> should.equal(Error(atom.AtomNotLoaded)) -} - -pub fn create_from_string_test() { - "ok" - |> atom.create_from_string - |> Ok - |> should.equal(atom.from_string("ok")) - - "expect" - |> atom.create_from_string - |> Ok - |> should.equal(atom.from_string("expect")) - - "this is another atom we have not seen before" - |> atom.create_from_string - |> Ok - |> should.equal(atom.from_string( - "this is another atom we have not seen before", - )) -} - -pub fn to_string_test() { - "ok" - |> atom.create_from_string - |> atom.to_string - |> should.equal("ok") - - "expect" - |> atom.create_from_string - |> atom.to_string - |> should.equal("expect") +if erlang { + import gleam/atom + import gleam/should + + pub fn from_string_test() { + atom.create_from_string("this is an existing atom") + + "this is an existing atom" + |> atom.from_string + |> should.be_ok + + "this is not an atom we have seen before" + |> atom.from_string + |> should.equal(Error(atom.AtomNotLoaded)) + } + + pub fn create_from_string_test() { + "ok" + |> atom.create_from_string + |> Ok + |> should.equal(atom.from_string("ok")) + + "expect" + |> atom.create_from_string + |> Ok + |> should.equal(atom.from_string("expect")) + + "this is another atom we have not seen before" + |> atom.create_from_string + |> Ok + |> should.equal(atom.from_string( + "this is another atom we have not seen before", + )) + } + + pub fn to_string_test() { + "ok" + |> atom.create_from_string + |> atom.to_string + |> should.equal("ok") + + "expect" + |> atom.create_from_string + |> atom.to_string + |> should.equal("expect") + } } diff --git a/test/gleam/base_test.gleam b/test/gleam/base_test.gleam index 3d2b7c6..80f0024 100644 --- a/test/gleam/base_test.gleam +++ b/test/gleam/base_test.gleam @@ -1,96 +1,98 @@ -import gleam/base -import gleam/bit_string.{BitString} -import gleam/io -import gleam/list -import gleam/should - -external fn list_to_binary(List(Int)) -> BitString = - "erlang" "list_to_binary" - -pub fn encode64_test() { - [255, 127, 254, 252] - |> list_to_binary() - |> base.encode64(True) - |> should.equal("/3/+/A==") - - [255, 127, 254, 252] - |> list_to_binary() - |> base.encode64(False) - |> should.equal("/3/+/A") - - [0, 0, 0] - |> list_to_binary() - |> base.encode64(True) - |> should.equal("AAAA") - - [] - |> list_to_binary() - |> base.encode64(True) - |> should.equal("") -} - -pub fn decode64_test() { - "/3/+/A==" - |> base.decode64() - |> should.equal(Ok(list_to_binary([255, 127, 254, 252]))) - - "/3/+/A" - |> base.decode64() - |> should.equal(Ok(list_to_binary([255, 127, 254, 252]))) - - "AAAA" - |> base.decode64() - |> should.equal(Ok(list_to_binary([0, 0, 0]))) - - "" - |> base.decode64() - |> should.equal(Ok(list_to_binary([]))) - - ")!" - |> base.decode64() - |> should.equal(Error(Nil)) -} - -pub fn url_encode64_test() { - [255, 127, 254, 252] - |> list_to_binary() - |> base.url_encode64(True) - |> should.equal("_3_-_A==") - - [255, 127, 254, 252] - |> list_to_binary() - |> base.url_encode64(False) - |> should.equal("_3_-_A") - - [0, 0, 0] - |> list_to_binary() - |> base.url_encode64(True) - |> should.equal("AAAA") - - [] - |> list_to_binary() - |> base.url_encode64(True) - |> should.equal("") -} - -pub fn url_decode64_test() { - "_3_-_A==" - |> base.url_decode64() - |> should.equal(Ok(list_to_binary([255, 127, 254, 252]))) - - "_3_-_A" - |> base.url_decode64() - |> should.equal(Ok(list_to_binary([255, 127, 254, 252]))) - - "AAAA" - |> base.url_decode64() - |> should.equal(Ok(list_to_binary([0, 0, 0]))) - - "" - |> base.url_decode64() - |> should.equal(Ok(list_to_binary([]))) - - ")!" - |> base.url_decode64() - |> should.equal(Error(Nil)) +if erlang { + import gleam/base + import gleam/bit_string.{BitString} + import gleam/io + import gleam/list + import gleam/should + + external fn list_to_binary(List(Int)) -> BitString = + "erlang" "list_to_binary" + + pub fn encode64_test() { + [255, 127, 254, 252] + |> list_to_binary() + |> base.encode64(True) + |> should.equal("/3/+/A==") + + [255, 127, 254, 252] + |> list_to_binary() + |> base.encode64(False) + |> should.equal("/3/+/A") + + [0, 0, 0] + |> list_to_binary() + |> base.encode64(True) + |> should.equal("AAAA") + + [] + |> list_to_binary() + |> base.encode64(True) + |> should.equal("") + } + + pub fn decode64_test() { + "/3/+/A==" + |> base.decode64() + |> should.equal(Ok(list_to_binary([255, 127, 254, 252]))) + + "/3/+/A" + |> base.decode64() + |> should.equal(Ok(list_to_binary([255, 127, 254, 252]))) + + "AAAA" + |> base.decode64() + |> should.equal(Ok(list_to_binary([0, 0, 0]))) + + "" + |> base.decode64() + |> should.equal(Ok(list_to_binary([]))) + + ")!" + |> base.decode64() + |> should.equal(Error(Nil)) + } + + pub fn url_encode64_test() { + [255, 127, 254, 252] + |> list_to_binary() + |> base.url_encode64(True) + |> should.equal("_3_-_A==") + + [255, 127, 254, 252] + |> list_to_binary() + |> base.url_encode64(False) + |> should.equal("_3_-_A") + + [0, 0, 0] + |> list_to_binary() + |> base.url_encode64(True) + |> should.equal("AAAA") + + [] + |> list_to_binary() + |> base.url_encode64(True) + |> should.equal("") + } + + pub fn url_decode64_test() { + "_3_-_A==" + |> base.url_decode64() + |> should.equal(Ok(list_to_binary([255, 127, 254, 252]))) + + "_3_-_A" + |> base.url_decode64() + |> should.equal(Ok(list_to_binary([255, 127, 254, 252]))) + + "AAAA" + |> base.url_decode64() + |> should.equal(Ok(list_to_binary([0, 0, 0]))) + + "" + |> base.url_decode64() + |> should.equal(Ok(list_to_binary([]))) + + ")!" + |> base.url_decode64() + |> should.equal(Error(Nil)) + } } diff --git a/test/gleam/bit_builder_test.gleam b/test/gleam/bit_builder_test.gleam index e1ec57b..fac962f 100644 --- a/test/gleam/bit_builder_test.gleam +++ b/test/gleam/bit_builder_test.gleam @@ -1,75 +1,77 @@ -import gleam/should -import gleam/bit_builder +if erlang { + import gleam/should + import gleam/bit_builder -pub fn builder_test() { - let data = - bit_builder.from_bit_string(<<1>>) - |> bit_builder.append(<<2>>) - |> bit_builder.append(<<3>>) - |> bit_builder.prepend(<<0>>) + pub fn builder_test() { + let data = + bit_builder.from_bit_string(<<1>>) + |> bit_builder.append(<<2>>) + |> bit_builder.append(<<3>>) + |> bit_builder.prepend(<<0>>) - data - |> bit_builder.to_bit_string - |> should.equal(<<0, 1, 2, 3>>) + data + |> bit_builder.to_bit_string + |> should.equal(<<0, 1, 2, 3>>) - data - |> bit_builder.byte_size - |> should.equal(4) -} + data + |> bit_builder.byte_size + |> should.equal(4) + } -pub fn builder_with_strings_test() { - let data = - bit_builder.from_bit_string(<<1>>) - |> bit_builder.append_string("2") - |> bit_builder.append_string("3") - |> bit_builder.prepend_string("0") + pub fn builder_with_strings_test() { + let data = + bit_builder.from_bit_string(<<1>>) + |> bit_builder.append_string("2") + |> bit_builder.append_string("3") + |> bit_builder.prepend_string("0") - data - |> bit_builder.to_bit_string - |> should.equal(<<"0":utf8, 1, "2":utf8, "3":utf8>>) + data + |> bit_builder.to_bit_string + |> should.equal(<<"0":utf8, 1, "2":utf8, "3":utf8>>) - data - |> bit_builder.byte_size - |> should.equal(4) -} + data + |> bit_builder.byte_size + |> should.equal(4) + } -pub fn builder_with_builders_test() { - let data = - bit_builder.from_bit_string(<<1>>) - |> bit_builder.append_builder(bit_builder.from_bit_string(<<2>>)) - |> bit_builder.append_builder(bit_builder.from_bit_string(<<3>>)) - |> bit_builder.prepend_builder(bit_builder.from_bit_string(<<0>>)) + pub fn builder_with_builders_test() { + let data = + bit_builder.from_bit_string(<<1>>) + |> bit_builder.append_builder(bit_builder.from_bit_string(<<2>>)) + |> bit_builder.append_builder(bit_builder.from_bit_string(<<3>>)) + |> bit_builder.prepend_builder(bit_builder.from_bit_string(<<0>>)) - data - |> bit_builder.to_bit_string - |> should.equal(<<0, 1, 2, 3>>) + data + |> bit_builder.to_bit_string + |> should.equal(<<0, 1, 2, 3>>) - data - |> bit_builder.byte_size - |> should.equal(4) -} + data + |> bit_builder.byte_size + |> should.equal(4) + } -pub fn concat_test() { - [ - bit_builder.from_bit_string(<<1, 2>>), - bit_builder.from_bit_string(<<3, 4>>), - bit_builder.from_bit_string(<<5, 6>>), - ] - |> bit_builder.concat - |> bit_builder.to_bit_string - |> should.equal(<<1, 2, 3, 4, 5, 6>>) -} + pub fn concat_test() { + [ + bit_builder.from_bit_string(<<1, 2>>), + bit_builder.from_bit_string(<<3, 4>>), + bit_builder.from_bit_string(<<5, 6>>), + ] + |> bit_builder.concat + |> bit_builder.to_bit_string + |> should.equal(<<1, 2, 3, 4, 5, 6>>) + } -pub fn from_bit_string_test() { - // Regression test: no additional modification of the builder - bit_builder.from_bit_string(<<>>) - |> bit_builder.to_bit_string - |> should.equal(<<>>) -} + pub fn from_bit_string_test() { + // Regression test: no additional modification of the builder + bit_builder.from_bit_string(<<>>) + |> bit_builder.to_bit_string + |> should.equal(<<>>) + } -pub fn from_string_test() { - // Regression test: no additional modification of the builder - bit_builder.from_string("") - |> bit_builder.to_bit_string - |> should.equal(<<>>) + pub fn from_string_test() { + // Regression test: no additional modification of the builder + bit_builder.from_string("") + |> bit_builder.to_bit_string + |> should.equal(<<>>) + } } diff --git a/test/gleam/bit_string_test.gleam b/test/gleam/bit_string_test.gleam index ea577fd..a69bc90 100644 --- a/test/gleam/bit_string_test.gleam +++ b/test/gleam/bit_string_test.gleam @@ -1,93 +1,98 @@ -import gleam/bit_string -import gleam/should - -pub fn length_test() { - bit_string.byte_size(bit_string.from_string("hello")) - |> should.equal(5) - - bit_string.byte_size(bit_string.from_string("")) - |> should.equal(0) -} - -pub fn append_test() { - bit_string.from_string("Test") - |> bit_string.append(bit_string.from_string(" Me")) - |> should.equal(bit_string.from_string("Test Me")) - - let Ok(zero_32bit) = bit_string.int_to_u32(0) - zero_32bit - |> bit_string.append(bit_string.from_string("")) - |> should.equal(zero_32bit) -} - -pub fn part_test() { - bit_string.from_string("hello") - |> bit_string.part(0, 5) - |> should.equal(Ok(bit_string.from_string("hello"))) - - bit_string.from_string("hello") - |> bit_string.part(0, 0) - |> should.equal(Ok(bit_string.from_string(""))) - - bit_string.from_string("hello") - |> bit_string.part(2, 2) - |> should.equal(Ok(bit_string.from_string("ll"))) - - bit_string.from_string("hello") - |> bit_string.part(5, -2) - |> should.equal(Ok(bit_string.from_string("lo"))) - - bit_string.from_string("") - |> bit_string.part(0, 0) - |> should.equal(Ok(bit_string.from_string(""))) - - bit_string.from_string("hello") - |> bit_string.part(6, 0) - |> should.equal(Error(Nil)) - - bit_string.from_string("hello") - |> bit_string.part(-1, 1) - |> should.equal(Error(Nil)) - - bit_string.from_string("hello") - |> bit_string.part(1, 6) - |> should.equal(Error(Nil)) -} - -pub fn u32_test() { - let Ok(bin) = bit_string.int_to_u32(0) - should.equal(4, bit_string.byte_size(bin)) - should.equal(Ok(0), bit_string.int_from_u32(bin)) - - let Ok(bin) = bit_string.int_to_u32(4294967295) - should.equal(4, bit_string.byte_size(bin)) - should.equal(Ok(4294967295), bit_string.int_from_u32(bin)) - - should.equal(Error(Nil), bit_string.int_from_u32(bit_string.from_string(""))) - should.equal( - Error(Nil), - bit_string.int_from_u32(bit_string.from_string("12345")), - ) -} - -pub fn to_string_test() { - <<>> - |> bit_string.to_string - |> should.equal(Ok("")) - - <<"":utf8>> - |> bit_string.to_string - |> should.equal(Ok("")) - - <<"Hello":utf8>> - |> bit_string.to_string - |> should.equal(Ok("Hello")) - - <<"ø":utf8>> - |> bit_string.to_string - |> should.equal(Ok("ø")) - - <<65535:16>> - |> bit_string.to_string - |> should.equal(Error(Nil)) +if erlang { + import gleam/bit_string + import gleam/should + + pub fn length_test() { + bit_string.byte_size(bit_string.from_string("hello")) + |> should.equal(5) + + bit_string.byte_size(bit_string.from_string("")) + |> should.equal(0) + } + + pub fn append_test() { + bit_string.from_string("Test") + |> bit_string.append(bit_string.from_string(" Me")) + |> should.equal(bit_string.from_string("Test Me")) + + let Ok(zero_32bit) = bit_string.int_to_u32(0) + zero_32bit + |> bit_string.append(bit_string.from_string("")) + |> should.equal(zero_32bit) + } + + pub fn part_test() { + bit_string.from_string("hello") + |> bit_string.part(0, 5) + |> should.equal(Ok(bit_string.from_string("hello"))) + + bit_string.from_string("hello") + |> bit_string.part(0, 0) + |> should.equal(Ok(bit_string.from_string(""))) + + bit_string.from_string("hello") + |> bit_string.part(2, 2) + |> should.equal(Ok(bit_string.from_string("ll"))) + + bit_string.from_string("hello") + |> bit_string.part(5, -2) + |> should.equal(Ok(bit_string.from_string("lo"))) + + bit_string.from_string("") + |> bit_string.part(0, 0) + |> should.equal(Ok(bit_string.from_string(""))) + + bit_string.from_string("hello") + |> bit_string.part(6, 0) + |> should.equal(Error(Nil)) + + bit_string.from_string("hello") + |> bit_string.part(-1, 1) + |> should.equal(Error(Nil)) + + bit_string.from_string("hello") + |> bit_string.part(1, 6) + |> should.equal(Error(Nil)) + } + + pub fn u32_test() { + let Ok(bin) = bit_string.int_to_u32(0) + should.equal(4, bit_string.byte_size(bin)) + should.equal(Ok(0), bit_string.int_from_u32(bin)) + + let Ok(bin) = bit_string.int_to_u32(4294967295) + should.equal(4, bit_string.byte_size(bin)) + should.equal(Ok(4294967295), bit_string.int_from_u32(bin)) + + should.equal( + Error(Nil), + bit_string.int_from_u32(bit_string.from_string("")), + ) + should.equal( + Error(Nil), + bit_string.int_from_u32(bit_string.from_string("12345")), + ) + } + + pub fn to_string_test() { + <<>> + |> bit_string.to_string + |> should.equal(Ok("")) + + <<"":utf8>> + |> bit_string.to_string + |> should.equal(Ok("")) + + <<"Hello":utf8>> + |> bit_string.to_string + |> should.equal(Ok("Hello")) + + <<"ø":utf8>> + |> bit_string.to_string + |> should.equal(Ok("ø")) + + <<65535:16>> + |> bit_string.to_string + |> should.equal(Error(Nil)) + } } diff --git a/test/gleam/bool_test.gleam b/test/gleam/bool_test.gleam index 3edea25..9658377 100644 --- a/test/gleam/bool_test.gleam +++ b/test/gleam/bool_test.gleam @@ -1,117 +1,119 @@ -import gleam/bool -import gleam/order -import gleam/should +if erlang { + import gleam/bool + import gleam/order + import gleam/should -pub fn negate_test() { - bool.negate(True) - |> should.be_false + pub fn negate_test() { + bool.negate(True) + |> should.be_false - bool.negate(False) - |> should.be_true -} + bool.negate(False) + |> should.be_true + } -pub fn nor_test() { - bool.nor(False, False) - |> should.be_true + pub fn nor_test() { + bool.nor(False, False) + |> should.be_true - bool.nor(False, True) - |> should.be_false + bool.nor(False, True) + |> should.be_false - bool.nor(True, False) - |> should.be_false + bool.nor(True, False) + |> should.be_false - bool.nor(True, True) - |> should.be_false -} + bool.nor(True, True) + |> should.be_false + } -pub fn nand_test() { - bool.nand(False, False) - |> should.be_true + pub fn nand_test() { + bool.nand(False, False) + |> should.be_true - bool.nand(False, True) - |> should.be_true + bool.nand(False, True) + |> should.be_true - bool.nand(True, False) - |> should.be_true + bool.nand(True, False) + |> should.be_true - bool.nand(True, True) - |> should.be_false -} + bool.nand(True, True) + |> should.be_false + } -pub fn exclusive_or_test() { - bool.exclusive_or(True, True) - |> should.be_false + pub fn exclusive_or_test() { + bool.exclusive_or(True, True) + |> should.be_false - bool.exclusive_or(False, False) - |> should.be_false + bool.exclusive_or(False, False) + |> should.be_false - bool.exclusive_or(True, False) - |> should.be_true + bool.exclusive_or(True, False) + |> should.be_true - bool.exclusive_or(False, True) - |> should.be_true -} + bool.exclusive_or(False, True) + |> should.be_true + } -pub fn exclusive_nor_test() { - bool.exclusive_nor(False, False) - |> should.be_true + pub fn exclusive_nor_test() { + bool.exclusive_nor(False, False) + |> should.be_true - bool.exclusive_nor(False, True) - |> should.be_false + bool.exclusive_nor(False, True) + |> should.be_false - bool.exclusive_nor(True, False) - |> should.be_false + bool.exclusive_nor(True, False) + |> should.be_false - bool.exclusive_nor(True, True) - |> should.be_true -} + bool.exclusive_nor(True, True) + |> should.be_true + } -pub fn compare_test() { - bool.compare(True, True) - |> should.equal(order.Eq) + pub fn compare_test() { + bool.compare(True, True) + |> should.equal(order.Eq) - bool.compare(True, False) - |> should.equal(order.Gt) + bool.compare(True, False) + |> should.equal(order.Gt) - bool.compare(False, False) - |> should.equal(order.Eq) + bool.compare(False, False) + |> should.equal(order.Eq) - bool.compare(False, True) - |> should.equal(order.Lt) -} + bool.compare(False, True) + |> should.equal(order.Lt) + } -pub fn max_test() { - bool.max(True, True) - |> should.equal(True) + pub fn max_test() { + bool.max(True, True) + |> should.equal(True) - bool.max(True, False) - |> should.equal(True) + bool.max(True, False) + |> should.equal(True) - bool.max(False, False) - |> should.equal(False) + bool.max(False, False) + |> should.equal(False) - bool.max(False, True) - |> should.equal(True) -} + bool.max(False, True) + |> should.equal(True) + } -pub fn min_test() { - bool.min(True, True) - |> should.equal(True) + pub fn min_test() { + bool.min(True, True) + |> should.equal(True) - bool.min(True, False) - |> should.equal(False) + bool.min(True, False) + |> should.equal(False) - bool.min(False, False) - |> should.equal(False) + bool.min(False, False) + |> should.equal(False) - bool.min(False, True) - |> should.equal(False) -} + bool.min(False, True) + |> should.equal(False) + } -pub fn to_int_test() { - bool.to_int(True) - |> should.equal(1) + pub fn to_int_test() { + bool.to_int(True) + |> should.equal(1) - bool.to_int(False) - |> should.equal(0) + bool.to_int(False) + |> should.equal(0) + } } diff --git a/test/gleam/dynamic_test.gleam b/test/gleam/dynamic_test.gleam index 2cc82b1..ca82802 100644 --- a/test/gleam/dynamic_test.gleam +++ b/test/gleam/dynamic_test.gleam @@ -1,744 +1,746 @@ -import gleam/bit_string -import gleam/dynamic -import gleam/atom -import gleam/list -import gleam/should -import gleam/result -import gleam/map -import gleam/option.{None, Some} - -pub fn bit_string_test() { - "" - |> dynamic.from - |> dynamic.bit_string - |> should.equal(Ok(<<"":utf8>>)) - - "Hello" - |> dynamic.from - |> dynamic.bit_string - |> should.equal(Ok(<<"Hello":utf8>>)) - - <<65535:16>> - |> dynamic.from - |> dynamic.bit_string - |> should.equal(Ok(<<65535:16>>)) - - 1 - |> dynamic.from - |> dynamic.bit_string - |> should.equal(Error("Expected a bit_string, got an int")) - - [] - |> dynamic.from - |> dynamic.bit_string - |> should.equal(Error("Expected a bit_string, got a list")) -} - -pub fn string_test() { - "" - |> dynamic.from - |> dynamic.string - |> should.equal(Ok("")) - - "Hello" - |> dynamic.from - |> dynamic.string - |> should.equal(Ok("Hello")) - - <<65535:16>> - |> dynamic.from - |> dynamic.string - |> should.equal(Error("Expected a string, got a bit_string")) - - 1 - |> dynamic.from - |> dynamic.string - |> should.equal(Error("Expected a bit_string, got an int")) - - [] - |> dynamic.from - |> dynamic.string - |> should.equal(Error("Expected a bit_string, got a list")) -} - -pub fn int_test() { - 1 - |> dynamic.from - |> dynamic.int - |> should.equal(Ok(1)) - - 2 - |> dynamic.from - |> dynamic.int - |> should.equal(Ok(2)) - - 1.0 - |> dynamic.from - |> dynamic.int - |> should.equal(Error("Expected an int, got a float")) - - [] - |> dynamic.from - |> dynamic.int - |> should.equal(Error("Expected an int, got a list")) -} - -pub fn float_test() { - 1.0 - |> dynamic.from - |> dynamic.float - |> should.equal(Ok(1.0)) - - 2.2 - |> dynamic.from - |> dynamic.float - |> should.equal(Ok(2.2)) - - 1 - |> dynamic.from - |> dynamic.float - |> should.equal(Error("Expected a float, got an int")) - - [] - |> dynamic.from - |> dynamic.float - |> should.equal(Error("Expected a float, got a list")) -} - -pub fn thunk_test() { - fn() { 1 } - |> dynamic.from - |> dynamic.thunk - |> should.be_ok - - fn() { 1 } - |> dynamic.from - |> dynamic.thunk - |> result.map(fn(f) { f() }) - |> should.equal(Ok(dynamic.from(1))) - - fn(x) { x } - |> dynamic.from - |> dynamic.thunk - |> should.be_error - - 1 - |> dynamic.from - |> dynamic.thunk - |> should.be_error - - [] - |> dynamic.from - |> dynamic.thunk - |> should.be_error -} - -pub fn bool_test() { - True - |> dynamic.from - |> dynamic.bool - |> should.equal(Ok(True)) - - False - |> dynamic.from - |> dynamic.bool - |> should.equal(Ok(False)) - - 1 - |> dynamic.from - |> dynamic.bool - |> should.equal(Error("Expected a bool, got an int")) - - [] - |> dynamic.from - |> dynamic.bool - |> should.equal(Error("Expected a bool, got a list")) -} - -pub fn atom_test() { - "" - |> atom.create_from_string - |> dynamic.from - |> dynamic.atom - |> should.equal(Ok(atom.create_from_string(""))) - - "ok" - |> atom.create_from_string - |> dynamic.from - |> dynamic.atom - |> should.equal(Ok(atom.create_from_string("ok"))) - - 1 - |> dynamic.from - |> dynamic.atom - |> should.be_error - - [] - |> dynamic.from - |> dynamic.atom - |> should.be_error -} - -pub fn typed_list_test() { - [] - |> dynamic.from - |> dynamic.typed_list(dynamic.string) - |> should.equal(Ok([])) - - [] - |> dynamic.from - |> dynamic.typed_list(dynamic.int) - |> should.equal(Ok([])) - - [1, 2, 3] - |> dynamic.from - |> dynamic.typed_list(dynamic.int) - |> should.equal(Ok([1, 2, 3])) - - [[1], [2], [3]] - |> dynamic.from - |> dynamic.typed_list(dynamic.typed_list(_, dynamic.int)) - |> should.equal(Ok([[1], [2], [3]])) - - 1 - |> dynamic.from - |> dynamic.typed_list(dynamic.string) - |> should.be_error - - 1.0 - |> dynamic.from - |> dynamic.typed_list(dynamic.int) - |> should.be_error - - [""] - |> dynamic.from - |> dynamic.typed_list(dynamic.int) - |> should.be_error - - [dynamic.from(1), dynamic.from("not an int")] - |> dynamic.from - |> dynamic.typed_list(dynamic.int) - |> should.be_error -} - -pub fn option_test() { - let Ok(null) = atom.from_string("null") - - 1 - |> dynamic.from - |> dynamic.option(dynamic.int) - |> should.equal(Ok(Some(1))) - null - |> dynamic.from - |> dynamic.option(dynamic.int) - |> should.equal(Ok(None)) - 1 - |> dynamic.from - |> dynamic.option(dynamic.string) - |> should.be_error -} - -pub fn field_test() { - let Ok(ok_atom) = atom.from_string("ok") - let Ok(error_atom) = atom.from_string("error") - - map.new() - |> map.insert(ok_atom, 1) - |> dynamic.from - |> dynamic.field(ok_atom) - |> should.equal(Ok(dynamic.from(1))) - - map.new() - |> map.insert(ok_atom, 3) - |> map.insert(error_atom, 1) - |> dynamic.from - |> dynamic.field(ok_atom) - |> should.equal(Ok(dynamic.from(3))) - - map.new() - |> dynamic.from - |> dynamic.field(ok_atom) - |> should.be_error - - 1 - |> dynamic.from - |> dynamic.field(ok_atom) - |> should.be_error - - [] - |> dynamic.from - |> dynamic.field([]) - |> should.be_error -} - -pub fn element_test() { - let Ok(ok_atom) = atom.from_string("ok") - let ok_one_tuple = #(ok_atom, 1) - - ok_one_tuple - |> dynamic.from - |> dynamic.element(0) - |> should.equal(Ok(dynamic.from(ok_atom))) - - ok_one_tuple - |> dynamic.from - |> dynamic.element(1) - |> should.equal(Ok(dynamic.from(1))) - - ok_one_tuple - |> dynamic.from - |> dynamic.element(2) - |> should.be_error - - ok_one_tuple - |> dynamic.from - |> dynamic.element(-1) - |> should.be_error - - 1 - |> dynamic.from - |> dynamic.element(0) - |> should.be_error - - map.new() - |> map.insert(1, ok_atom) - |> dynamic.from - |> dynamic.element(0) - |> should.be_error -} - -pub fn tuple2_test() { - #(1, 2) - |> dynamic.from - |> dynamic.tuple2 - |> should.equal(Ok(#(dynamic.from(1), dynamic.from(2)))) - - #(1, "") - |> dynamic.from - |> dynamic.tuple2 - |> should.equal(Ok(#(dynamic.from(1), dynamic.from("")))) - - #(1, 2, 3) - |> dynamic.from - |> dynamic.tuple2 - |> should.equal(Error("Expected a 2 element tuple, got a 3 element tuple")) - - 1 - |> dynamic.from - |> dynamic.tuple2 - |> should.equal(Error("Expected a 2 element tuple, got an int")) -} - -pub fn typed_tuple2_test() { - #(1, 2) - |> dynamic.from - |> dynamic.typed_tuple2(dynamic.int, dynamic.int) - |> should.equal(Ok(#(1, 2))) - - #(1, "") - |> dynamic.from - |> dynamic.typed_tuple2(dynamic.int, dynamic.string) - |> should.equal(Ok(#(1, ""))) - - #(1, "") - |> dynamic.from - |> dynamic.typed_tuple2(dynamic.int, dynamic.int) - |> should.equal(Error("Expected an int, got a binary")) - - #(1, 2, 3) - |> dynamic.from - |> dynamic.typed_tuple2(dynamic.int, dynamic.int) - |> should.equal(Error("Expected a 2 element tuple, got a 3 element tuple")) - - 1 - |> dynamic.from - |> dynamic.typed_tuple2(dynamic.int, dynamic.int) - |> should.equal(Error("Expected a 2 element tuple, got an int")) -} - -pub fn tuple3_test() { - #(1, 2, 3) - |> dynamic.from - |> dynamic.tuple3 - |> should.equal(Ok(#(dynamic.from(1), dynamic.from(2), dynamic.from(3)))) - - #(1, "", 3.0) - |> dynamic.from - |> dynamic.tuple3 - |> should.equal(Ok(#(dynamic.from(1), dynamic.from(""), dynamic.from(3.0)))) - - #(1, 2) - |> dynamic.from - |> dynamic.tuple3 - |> should.equal(Error("Expected a 3 element tuple, got a 2 element tuple")) - - 1 - |> dynamic.from - |> dynamic.tuple3 - |> should.equal(Error("Expected a 3 element tuple, got an int")) -} - -pub fn typed_tuple3_test() { - #(1, 2, 3) - |> dynamic.from - |> dynamic.typed_tuple3(dynamic.int, dynamic.int, dynamic.int) - |> should.equal(Ok(#(1, 2, 3))) - - #(1, "", 3.0) - |> dynamic.from - |> dynamic.typed_tuple3(dynamic.int, dynamic.string, dynamic.float) - |> should.equal(Ok(#(1, "", 3.0))) - - #(1, 2, "") - |> dynamic.from - |> dynamic.typed_tuple3(dynamic.int, dynamic.int, dynamic.int) - |> should.equal(Error("Expected an int, got a binary")) - - #(1, 2) - |> dynamic.from - |> dynamic.typed_tuple3(dynamic.int, dynamic.int, dynamic.int) - |> should.equal(Error("Expected a 3 element tuple, got a 2 element tuple")) - - 1 - |> dynamic.from - |> dynamic.typed_tuple3(dynamic.int, dynamic.int, dynamic.int) - |> should.equal(Error("Expected a 3 element tuple, got an int")) -} - -pub fn tuple4_test() { - #(1, 2, 3, 4) - |> dynamic.from - |> dynamic.tuple4 - |> should.equal(Ok(#( - dynamic.from(1), - dynamic.from(2), - dynamic.from(3), - dynamic.from(4), - ))) - - #(1, "", 3.0, 4) - |> dynamic.from - |> dynamic.tuple4 - |> should.equal(Ok(#( - dynamic.from(1), - dynamic.from(""), - dynamic.from(3.0), - dynamic.from(4), - ))) - - #(1, 2) - |> dynamic.from - |> dynamic.tuple4 - |> should.equal(Error("Expected a 4 element tuple, got a 2 element tuple")) - - 1 - |> dynamic.from - |> dynamic.tuple4 - |> should.equal(Error("Expected a 4 element tuple, got an int")) -} - -pub fn typed_tuple4_test() { - #(1, 2, 3, 4) - |> dynamic.from - |> dynamic.typed_tuple4(dynamic.int, dynamic.int, dynamic.int, dynamic.int) - |> should.equal(Ok(#(1, 2, 3, 4))) - - #(1, "", 3.0, 4) - |> dynamic.from - |> dynamic.typed_tuple4( - dynamic.int, - dynamic.string, - dynamic.float, - dynamic.int, - ) - |> should.equal(Ok(#(1, "", 3.0, 4))) - - #(1, 2, 3, "") - |> dynamic.from - |> dynamic.typed_tuple4(dynamic.int, dynamic.int, dynamic.int, dynamic.int) - |> should.equal(Error("Expected an int, got a binary")) - - #(1, 2) - |> dynamic.from - |> dynamic.typed_tuple4(dynamic.int, dynamic.int, dynamic.int, dynamic.int) - |> should.equal(Error("Expected a 4 element tuple, got a 2 element tuple")) - - 1 - |> dynamic.from - |> dynamic.typed_tuple4(dynamic.int, dynamic.int, dynamic.int, dynamic.int) - |> should.equal(Error("Expected a 4 element tuple, got an int")) -} - -pub fn tuple5_test() { - #(1, 2, 3, 4, 5) - |> dynamic.from - |> dynamic.tuple5 - |> should.equal(Ok(#( - dynamic.from(1), - dynamic.from(2), - dynamic.from(3), - dynamic.from(4), - dynamic.from(5), - ))) - - #(1, "", 3.0, 4, 5) - |> dynamic.from - |> dynamic.tuple5 - |> should.equal(Ok(#( - dynamic.from(1), - dynamic.from(""), - dynamic.from(3.0), - dynamic.from(4), - dynamic.from(5), - ))) - - #(1, 2) - |> dynamic.from - |> dynamic.tuple5 - |> should.equal(Error("Expected a 5 element tuple, got a 2 element tuple")) - - 1 - |> dynamic.from - |> dynamic.tuple5 - |> should.equal(Error("Expected a 5 element tuple, got an int")) -} - -pub fn typed_tuple5_test() { - #(1, 2, 3, 4, 5) - |> dynamic.from - |> dynamic.typed_tuple5( - dynamic.int, - dynamic.int, - dynamic.int, - dynamic.int, - dynamic.int, - ) - |> should.equal(Ok(#(1, 2, 3, 4, 5))) - - #(1, "", 3.0, 4, 5) - |> dynamic.from - |> dynamic.typed_tuple5( - dynamic.int, - dynamic.string, - dynamic.float, - dynamic.int, - dynamic.int, - ) - |> should.equal(Ok(#(1, "", 3.0, 4, 5))) - - #(1, 2, 3, 4, "") - |> dynamic.from - |> dynamic.typed_tuple5( - dynamic.int, - dynamic.int, - dynamic.int, - dynamic.int, - dynamic.int, - ) - |> should.equal(Error("Expected an int, got a binary")) - - #(1, 2) - |> dynamic.from - |> dynamic.typed_tuple5( - dynamic.int, - dynamic.int, - dynamic.int, - dynamic.int, - dynamic.int, - ) - |> should.equal(Error("Expected a 5 element tuple, got a 2 element tuple")) - - 1 - |> dynamic.from - |> dynamic.typed_tuple5( - dynamic.int, - dynamic.int, - dynamic.int, - dynamic.int, - dynamic.int, - ) - |> should.equal(Error("Expected a 5 element tuple, got an int")) -} - -pub fn tuple6_test() { - #(1, 2, 3, 4, 5, 6) - |> dynamic.from - |> dynamic.tuple6 - |> should.equal(Ok(#( - dynamic.from(1), - dynamic.from(2), - dynamic.from(3), - dynamic.from(4), - dynamic.from(5), - dynamic.from(6), - ))) - - #(1, "", 3.0, 4, 5, 6) - |> dynamic.from - |> dynamic.tuple6 - |> should.equal(Ok(#( - dynamic.from(1), - dynamic.from(""), - dynamic.from(3.0), - dynamic.from(4), - dynamic.from(5), - dynamic.from(6), - ))) - - #(1, 2) - |> dynamic.from - |> dynamic.tuple6 - |> should.equal(Error("Expected a 6 element tuple, got a 2 element tuple")) - - 1 - |> dynamic.from - |> dynamic.tuple6 - |> should.equal(Error("Expected a 6 element tuple, got an int")) -} - -pub fn typed_tuple6_test() { - #(1, 2, 3, 4, 5, 6) - |> dynamic.from - |> dynamic.typed_tuple6( - dynamic.int, - dynamic.int, - dynamic.int, - dynamic.int, - dynamic.int, - dynamic.int, - ) - |> should.equal(Ok(#(1, 2, 3, 4, 5, 6))) - - #(1, "", 3.0, 4, 5, 6) - |> dynamic.from - |> dynamic.typed_tuple6( - dynamic.int, - dynamic.string, - dynamic.float, - dynamic.int, - dynamic.int, - dynamic.int, - ) - |> should.equal(Ok(#(1, "", 3.0, 4, 5, 6))) - - #(1, 2, 3, 4, 5, "") - |> dynamic.from - |> dynamic.typed_tuple6( - dynamic.int, - dynamic.int, - dynamic.int, - dynamic.int, - dynamic.int, - dynamic.int, - ) - |> should.equal(Error("Expected an int, got a binary")) - - #(1, 2) - |> dynamic.from - |> dynamic.typed_tuple6( - dynamic.int, - dynamic.int, - dynamic.int, - dynamic.int, - dynamic.int, - dynamic.int, - ) - |> should.equal(Error("Expected a 6 element tuple, got a 2 element tuple")) - - 1 - |> dynamic.from - |> dynamic.typed_tuple6( - dynamic.int, - dynamic.int, - dynamic.int, - dynamic.int, - dynamic.int, - dynamic.int, - ) - |> should.equal(Error("Expected a 6 element tuple, got an int")) -} - -pub fn map_test() { - map.new() - |> dynamic.from - |> dynamic.map - |> should.equal(Ok(map.new())) - - 1 - |> dynamic.from - |> dynamic.map - |> should.equal(Error("Expected a map, got an int")) -} - -pub fn list_test() { - [] - |> dynamic.from - |> dynamic.list - |> should.equal(Ok([])) - - [1, 2] - |> dynamic.from - |> dynamic.list - |> should.equal(Ok([dynamic.from(1), dynamic.from(2)])) - - [dynamic.from(1), dynamic.from(2.0)] - |> dynamic.from - |> dynamic.list - |> should.equal(Ok([dynamic.from(1), dynamic.from(2.0)])) - - 1 - |> dynamic.from - |> dynamic.list - |> should.equal(Error("Expected a list, got an int")) -} - -pub fn result_test() { - Ok(1) - |> dynamic.from - |> dynamic.result - |> should.equal(Ok(Ok(dynamic.from(1)))) - - Error("error") - |> dynamic.from - |> dynamic.result - |> should.equal(Ok(Error(dynamic.from("error")))) - - 1 - |> dynamic.from - |> dynamic.result - |> should.equal(Error("Expected a 2 element tuple, got an int")) - - let tag = atom.create_from_string("bad") - - #(tag, "value") - |> dynamic.from - |> dynamic.result - |> should.equal(Error("Expected a tag of \"ok\" or \"error\", got \"bad\"")) -} - -pub fn typed_result_test() { - Ok(1) - |> dynamic.from - |> dynamic.typed_result(ok: dynamic.int, error: dynamic.string) - |> should.equal(Ok(Ok(1))) - - Error("error") - |> dynamic.from - |> dynamic.typed_result(ok: dynamic.int, error: dynamic.string) - |> should.equal(Ok(Error("error"))) - - Ok("1") - |> dynamic.from - |> dynamic.typed_result(ok: dynamic.int, error: dynamic.string) - |> should.equal(Error("Expected an int, got a binary")) - - Error(1) - |> dynamic.from - |> dynamic.typed_result(ok: dynamic.int, error: dynamic.string) - |> should.equal(Error("Expected a bit_string, got an int")) - - 1 - |> dynamic.from - |> dynamic.typed_result(ok: dynamic.int, error: dynamic.string) - |> should.equal(Error("Expected a 2 element tuple, got an int")) +if erlang { + import gleam/bit_string + import gleam/dynamic + import gleam/atom + import gleam/list + import gleam/should + import gleam/result + import gleam/map + import gleam/option.{None, Some} + + pub fn bit_string_test() { + "" + |> dynamic.from + |> dynamic.bit_string + |> should.equal(Ok(<<"":utf8>>)) + + "Hello" + |> dynamic.from + |> dynamic.bit_string + |> should.equal(Ok(<<"Hello":utf8>>)) + + <<65535:16>> + |> dynamic.from + |> dynamic.bit_string + |> should.equal(Ok(<<65535:16>>)) + + 1 + |> dynamic.from + |> dynamic.bit_string + |> should.equal(Error("Expected a bit_string, got an int")) + + [] + |> dynamic.from + |> dynamic.bit_string + |> should.equal(Error("Expected a bit_string, got a list")) + } + + pub fn string_test() { + "" + |> dynamic.from + |> dynamic.string + |> should.equal(Ok("")) + + "Hello" + |> dynamic.from + |> dynamic.string + |> should.equal(Ok("Hello")) + + <<65535:16>> + |> dynamic.from + |> dynamic.string + |> should.equal(Error("Expected a string, got a bit_string")) + + 1 + |> dynamic.from + |> dynamic.string + |> should.equal(Error("Expected a bit_string, got an int")) + + [] + |> dynamic.from + |> dynamic.string + |> should.equal(Error("Expected a bit_string, got a list")) + } + + pub fn int_test() { + 1 + |> dynamic.from + |> dynamic.int + |> should.equal(Ok(1)) + + 2 + |> dynamic.from + |> dynamic.int + |> should.equal(Ok(2)) + + 1.0 + |> dynamic.from + |> dynamic.int + |> should.equal(Error("Expected an int, got a float")) + + [] + |> dynamic.from + |> dynamic.int + |> should.equal(Error("Expected an int, got a list")) + } + + pub fn float_test() { + 1.0 + |> dynamic.from + |> dynamic.float + |> should.equal(Ok(1.0)) + + 2.2 + |> dynamic.from + |> dynamic.float + |> should.equal(Ok(2.2)) + + 1 + |> dynamic.from + |> dynamic.float + |> should.equal(Error("Expected a float, got an int")) + + [] + |> dynamic.from + |> dynamic.float + |> should.equal(Error("Expected a float, got a list")) + } + + pub fn thunk_test() { + fn() { 1 } + |> dynamic.from + |> dynamic.thunk + |> should.be_ok + + fn() { 1 } + |> dynamic.from + |> dynamic.thunk + |> result.map(fn(f) { f() }) + |> should.equal(Ok(dynamic.from(1))) + + fn(x) { x } + |> dynamic.from + |> dynamic.thunk + |> should.be_error + + 1 + |> dynamic.from + |> dynamic.thunk + |> should.be_error + + [] + |> dynamic.from + |> dynamic.thunk + |> should.be_error + } + + pub fn bool_test() { + True + |> dynamic.from + |> dynamic.bool + |> should.equal(Ok(True)) + + False + |> dynamic.from + |> dynamic.bool + |> should.equal(Ok(False)) + + 1 + |> dynamic.from + |> dynamic.bool + |> should.equal(Error("Expected a bool, got an int")) + + [] + |> dynamic.from + |> dynamic.bool + |> should.equal(Error("Expected a bool, got a list")) + } + + pub fn atom_test() { + "" + |> atom.create_from_string + |> dynamic.from + |> dynamic.atom + |> should.equal(Ok(atom.create_from_string(""))) + + "ok" + |> atom.create_from_string + |> dynamic.from + |> dynamic.atom + |> should.equal(Ok(atom.create_from_string("ok"))) + + 1 + |> dynamic.from + |> dynamic.atom + |> should.be_error + + [] + |> dynamic.from + |> dynamic.atom + |> should.be_error + } + + pub fn typed_list_test() { + [] + |> dynamic.from + |> dynamic.typed_list(dynamic.string) + |> should.equal(Ok([])) + + [] + |> dynamic.from + |> dynamic.typed_list(dynamic.int) + |> should.equal(Ok([])) + + [1, 2, 3] + |> dynamic.from + |> dynamic.typed_list(dynamic.int) + |> should.equal(Ok([1, 2, 3])) + + [[1], [2], [3]] + |> dynamic.from + |> dynamic.typed_list(dynamic.typed_list(_, dynamic.int)) + |> should.equal(Ok([[1], [2], [3]])) + + 1 + |> dynamic.from + |> dynamic.typed_list(dynamic.string) + |> should.be_error + + 1.0 + |> dynamic.from + |> dynamic.typed_list(dynamic.int) + |> should.be_error + + [""] + |> dynamic.from + |> dynamic.typed_list(dynamic.int) + |> should.be_error + + [dynamic.from(1), dynamic.from("not an int")] + |> dynamic.from + |> dynamic.typed_list(dynamic.int) + |> should.be_error + } + + pub fn option_test() { + let Ok(null) = atom.from_string("null") + + 1 + |> dynamic.from + |> dynamic.option(dynamic.int) + |> should.equal(Ok(Some(1))) + null + |> dynamic.from + |> dynamic.option(dynamic.int) + |> should.equal(Ok(None)) + 1 + |> dynamic.from + |> dynamic.option(dynamic.string) + |> should.be_error + } + + pub fn field_test() { + let Ok(ok_atom) = atom.from_string("ok") + let Ok(error_atom) = atom.from_string("error") + + map.new() + |> map.insert(ok_atom, 1) + |> dynamic.from + |> dynamic.field(ok_atom) + |> should.equal(Ok(dynamic.from(1))) + + map.new() + |> map.insert(ok_atom, 3) + |> map.insert(error_atom, 1) + |> dynamic.from + |> dynamic.field(ok_atom) + |> should.equal(Ok(dynamic.from(3))) + + map.new() + |> dynamic.from + |> dynamic.field(ok_atom) + |> should.be_error + + 1 + |> dynamic.from + |> dynamic.field(ok_atom) + |> should.be_error + + [] + |> dynamic.from + |> dynamic.field([]) + |> should.be_error + } + + pub fn element_test() { + let Ok(ok_atom) = atom.from_string("ok") + let ok_one_tuple = #(ok_atom, 1) + + ok_one_tuple + |> dynamic.from + |> dynamic.element(0) + |> should.equal(Ok(dynamic.from(ok_atom))) + + ok_one_tuple + |> dynamic.from + |> dynamic.element(1) + |> should.equal(Ok(dynamic.from(1))) + + ok_one_tuple + |> dynamic.from + |> dynamic.element(2) + |> should.be_error + + ok_one_tuple + |> dynamic.from + |> dynamic.element(-1) + |> should.be_error + + 1 + |> dynamic.from + |> dynamic.element(0) + |> should.be_error + + map.new() + |> map.insert(1, ok_atom) + |> dynamic.from + |> dynamic.element(0) + |> should.be_error + } + + pub fn tuple2_test() { + #(1, 2) + |> dynamic.from + |> dynamic.tuple2 + |> should.equal(Ok(#(dynamic.from(1), dynamic.from(2)))) + + #(1, "") + |> dynamic.from + |> dynamic.tuple2 + |> should.equal(Ok(#(dynamic.from(1), dynamic.from("")))) + + #(1, 2, 3) + |> dynamic.from + |> dynamic.tuple2 + |> should.equal(Error("Expected a 2 element tuple, got a 3 element tuple")) + + 1 + |> dynamic.from + |> dynamic.tuple2 + |> should.equal(Error("Expected a 2 element tuple, got an int")) + } + + pub fn typed_tuple2_test() { + #(1, 2) + |> dynamic.from + |> dynamic.typed_tuple2(dynamic.int, dynamic.int) + |> should.equal(Ok(#(1, 2))) + + #(1, "") + |> dynamic.from + |> dynamic.typed_tuple2(dynamic.int, dynamic.string) + |> should.equal(Ok(#(1, ""))) + + #(1, "") + |> dynamic.from + |> dynamic.typed_tuple2(dynamic.int, dynamic.int) + |> should.equal(Error("Expected an int, got a binary")) + + #(1, 2, 3) + |> dynamic.from + |> dynamic.typed_tuple2(dynamic.int, dynamic.int) + |> should.equal(Error("Expected a 2 element tuple, got a 3 element tuple")) + + 1 + |> dynamic.from + |> dynamic.typed_tuple2(dynamic.int, dynamic.int) + |> should.equal(Error("Expected a 2 element tuple, got an int")) + } + + pub fn tuple3_test() { + #(1, 2, 3) + |> dynamic.from + |> dynamic.tuple3 + |> should.equal(Ok(#(dynamic.from(1), dynamic.from(2), dynamic.from(3)))) + + #(1, "", 3.0) + |> dynamic.from + |> dynamic.tuple3 + |> should.equal(Ok(#(dynamic.from(1), dynamic.from(""), dynamic.from(3.0)))) + + #(1, 2) + |> dynamic.from + |> dynamic.tuple3 + |> should.equal(Error("Expected a 3 element tuple, got a 2 element tuple")) + + 1 + |> dynamic.from + |> dynamic.tuple3 + |> should.equal(Error("Expected a 3 element tuple, got an int")) + } + + pub fn typed_tuple3_test() { + #(1, 2, 3) + |> dynamic.from + |> dynamic.typed_tuple3(dynamic.int, dynamic.int, dynamic.int) + |> should.equal(Ok(#(1, 2, 3))) + + #(1, "", 3.0) + |> dynamic.from + |> dynamic.typed_tuple3(dynamic.int, dynamic.string, dynamic.float) + |> should.equal(Ok(#(1, "", 3.0))) + + #(1, 2, "") + |> dynamic.from + |> dynamic.typed_tuple3(dynamic.int, dynamic.int, dynamic.int) + |> should.equal(Error("Expected an int, got a binary")) + + #(1, 2) + |> dynamic.from + |> dynamic.typed_tuple3(dynamic.int, dynamic.int, dynamic.int) + |> should.equal(Error("Expected a 3 element tuple, got a 2 element tuple")) + + 1 + |> dynamic.from + |> dynamic.typed_tuple3(dynamic.int, dynamic.int, dynamic.int) + |> should.equal(Error("Expected a 3 element tuple, got an int")) + } + + pub fn tuple4_test() { + #(1, 2, 3, 4) + |> dynamic.from + |> dynamic.tuple4 + |> should.equal(Ok(#( + dynamic.from(1), + dynamic.from(2), + dynamic.from(3), + dynamic.from(4), + ))) + + #(1, "", 3.0, 4) + |> dynamic.from + |> dynamic.tuple4 + |> should.equal(Ok(#( + dynamic.from(1), + dynamic.from(""), + dynamic.from(3.0), + dynamic.from(4), + ))) + + #(1, 2) + |> dynamic.from + |> dynamic.tuple4 + |> should.equal(Error("Expected a 4 element tuple, got a 2 element tuple")) + + 1 + |> dynamic.from + |> dynamic.tuple4 + |> should.equal(Error("Expected a 4 element tuple, got an int")) + } + + pub fn typed_tuple4_test() { + #(1, 2, 3, 4) + |> dynamic.from + |> dynamic.typed_tuple4(dynamic.int, dynamic.int, dynamic.int, dynamic.int) + |> should.equal(Ok(#(1, 2, 3, 4))) + + #(1, "", 3.0, 4) + |> dynamic.from + |> dynamic.typed_tuple4( + dynamic.int, + dynamic.string, + dynamic.float, + dynamic.int, + ) + |> should.equal(Ok(#(1, "", 3.0, 4))) + + #(1, 2, 3, "") + |> dynamic.from + |> dynamic.typed_tuple4(dynamic.int, dynamic.int, dynamic.int, dynamic.int) + |> should.equal(Error("Expected an int, got a binary")) + + #(1, 2) + |> dynamic.from + |> dynamic.typed_tuple4(dynamic.int, dynamic.int, dynamic.int, dynamic.int) + |> should.equal(Error("Expected a 4 element tuple, got a 2 element tuple")) + + 1 + |> dynamic.from + |> dynamic.typed_tuple4(dynamic.int, dynamic.int, dynamic.int, dynamic.int) + |> should.equal(Error("Expected a 4 element tuple, got an int")) + } + + pub fn tuple5_test() { + #(1, 2, 3, 4, 5) + |> dynamic.from + |> dynamic.tuple5 + |> should.equal(Ok(#( + dynamic.from(1), + dynamic.from(2), + dynamic.from(3), + dynamic.from(4), + dynamic.from(5), + ))) + + #(1, "", 3.0, 4, 5) + |> dynamic.from + |> dynamic.tuple5 + |> should.equal(Ok(#( + dynamic.from(1), + dynamic.from(""), + dynamic.from(3.0), + dynamic.from(4), + dynamic.from(5), + ))) + + #(1, 2) + |> dynamic.from + |> dynamic.tuple5 + |> should.equal(Error("Expected a 5 element tuple, got a 2 element tuple")) + + 1 + |> dynamic.from + |> dynamic.tuple5 + |> should.equal(Error("Expected a 5 element tuple, got an int")) + } + + pub fn typed_tuple5_test() { + #(1, 2, 3, 4, 5) + |> dynamic.from + |> dynamic.typed_tuple5( + dynamic.int, + dynamic.int, + dynamic.int, + dynamic.int, + dynamic.int, + ) + |> should.equal(Ok(#(1, 2, 3, 4, 5))) + + #(1, "", 3.0, 4, 5) + |> dynamic.from + |> dynamic.typed_tuple5( + dynamic.int, + dynamic.string, + dynamic.float, + dynamic.int, + dynamic.int, + ) + |> should.equal(Ok(#(1, "", 3.0, 4, 5))) + + #(1, 2, 3, 4, "") + |> dynamic.from + |> dynamic.typed_tuple5( + dynamic.int, + dynamic.int, + dynamic.int, + dynamic.int, + dynamic.int, + ) + |> should.equal(Error("Expected an int, got a binary")) + + #(1, 2) + |> dynamic.from + |> dynamic.typed_tuple5( + dynamic.int, + dynamic.int, + dynamic.int, + dynamic.int, + dynamic.int, + ) + |> should.equal(Error("Expected a 5 element tuple, got a 2 element tuple")) + + 1 + |> dynamic.from + |> dynamic.typed_tuple5( + dynamic.int, + dynamic.int, + dynamic.int, + dynamic.int, + dynamic.int, + ) + |> should.equal(Error("Expected a 5 element tuple, got an int")) + } + + pub fn tuple6_test() { + #(1, 2, 3, 4, 5, 6) + |> dynamic.from + |> dynamic.tuple6 + |> should.equal(Ok(#( + dynamic.from(1), + dynamic.from(2), + dynamic.from(3), + dynamic.from(4), + dynamic.from(5), + dynamic.from(6), + ))) + + #(1, "", 3.0, 4, 5, 6) + |> dynamic.from + |> dynamic.tuple6 + |> should.equal(Ok(#( + dynamic.from(1), + dynamic.from(""), + dynamic.from(3.0), + dynamic.from(4), + dynamic.from(5), + dynamic.from(6), + ))) + + #(1, 2) + |> dynamic.from + |> dynamic.tuple6 + |> should.equal(Error("Expected a 6 element tuple, got a 2 element tuple")) + + 1 + |> dynamic.from + |> dynamic.tuple6 + |> should.equal(Error("Expected a 6 element tuple, got an int")) + } + + pub fn typed_tuple6_test() { + #(1, 2, 3, 4, 5, 6) + |> dynamic.from + |> dynamic.typed_tuple6( + dynamic.int, + dynamic.int, + dynamic.int, + dynamic.int, + dynamic.int, + dynamic.int, + ) + |> should.equal(Ok(#(1, 2, 3, 4, 5, 6))) + + #(1, "", 3.0, 4, 5, 6) + |> dynamic.from + |> dynamic.typed_tuple6( + dynamic.int, + dynamic.string, + dynamic.float, + dynamic.int, + dynamic.int, + dynamic.int, + ) + |> should.equal(Ok(#(1, "", 3.0, 4, 5, 6))) + + #(1, 2, 3, 4, 5, "") + |> dynamic.from + |> dynamic.typed_tuple6( + dynamic.int, + dynamic.int, + dynamic.int, + dynamic.int, + dynamic.int, + dynamic.int, + ) + |> should.equal(Error("Expected an int, got a binary")) + + #(1, 2) + |> dynamic.from + |> dynamic.typed_tuple6( + dynamic.int, + dynamic.int, + dynamic.int, + dynamic.int, + dynamic.int, + dynamic.int, + ) + |> should.equal(Error("Expected a 6 element tuple, got a 2 element tuple")) + + 1 + |> dynamic.from + |> dynamic.typed_tuple6( + dynamic.int, + dynamic.int, + dynamic.int, + dynamic.int, + dynamic.int, + dynamic.int, + ) + |> should.equal(Error("Expected a 6 element tuple, got an int")) + } + + pub fn map_test() { + map.new() + |> dynamic.from + |> dynamic.map + |> should.equal(Ok(map.new())) + + 1 + |> dynamic.from + |> dynamic.map + |> should.equal(Error("Expected a map, got an int")) + } + + pub fn list_test() { + [] + |> dynamic.from + |> dynamic.list + |> should.equal(Ok([])) + + [1, 2] + |> dynamic.from + |> dynamic.list + |> should.equal(Ok([dynamic.from(1), dynamic.from(2)])) + + [dynamic.from(1), dynamic.from(2.0)] + |> dynamic.from + |> dynamic.list + |> should.equal(Ok([dynamic.from(1), dynamic.from(2.0)])) + + 1 + |> dynamic.from + |> dynamic.list + |> should.equal(Error("Expected a list, got an int")) + } + + pub fn result_test() { + Ok(1) + |> dynamic.from + |> dynamic.result + |> should.equal(Ok(Ok(dynamic.from(1)))) + + Error("error") + |> dynamic.from + |> dynamic.result + |> should.equal(Ok(Error(dynamic.from("error")))) + + 1 + |> dynamic.from + |> dynamic.result + |> should.equal(Error("Expected a 2 element tuple, got an int")) + + let tag = atom.create_from_string("bad") + + #(tag, "value") + |> dynamic.from + |> dynamic.result + |> should.equal(Error("Expected a tag of \"ok\" or \"error\", got \"bad\"")) + } + + pub fn typed_result_test() { + Ok(1) + |> dynamic.from + |> dynamic.typed_result(ok: dynamic.int, error: dynamic.string) + |> should.equal(Ok(Ok(1))) + + Error("error") + |> dynamic.from + |> dynamic.typed_result(ok: dynamic.int, error: dynamic.string) + |> should.equal(Ok(Error("error"))) + + Ok("1") + |> dynamic.from + |> dynamic.typed_result(ok: dynamic.int, error: dynamic.string) + |> should.equal(Error("Expected an int, got a binary")) + + Error(1) + |> dynamic.from + |> dynamic.typed_result(ok: dynamic.int, error: dynamic.string) + |> should.equal(Error("Expected a bit_string, got an int")) + + 1 + |> dynamic.from + |> dynamic.typed_result(ok: dynamic.int, error: dynamic.string) + |> should.equal(Error("Expected a 2 element tuple, got an int")) + } } diff --git a/test/gleam/float_test.gleam b/test/gleam/float_test.gleam index 3e0e6f6..79dba56 100644 --- a/test/gleam/float_test.gleam +++ b/test/gleam/float_test.gleam @@ -1,274 +1,276 @@ -import gleam/should -import gleam/float -import gleam/order - -pub fn parse_test() { - "1.23" - |> float.parse - |> should.equal(Ok(1.23)) - - "5.0" - |> float.parse - |> should.equal(Ok(5.0)) - - "0.123456789" - |> float.parse - |> should.equal(Ok(0.123456789)) - - "" - |> float.parse - |> should.equal(Error(Nil)) - - "what" - |> float.parse - |> should.equal(Error(Nil)) - - "1" - |> float.parse - |> should.equal(Error(Nil)) -} +if erlang { + import gleam/should + import gleam/float + import gleam/order + + pub fn parse_test() { + "1.23" + |> float.parse + |> should.equal(Ok(1.23)) + + "5.0" + |> float.parse + |> should.equal(Ok(5.0)) + + "0.123456789" + |> float.parse + |> should.equal(Ok(0.123456789)) + + "" + |> float.parse + |> should.equal(Error(Nil)) + + "what" + |> float.parse + |> should.equal(Error(Nil)) + + "1" + |> float.parse + |> should.equal(Error(Nil)) + } + + pub fn to_string_test() { + 123.0 + |> float.to_string + |> should.equal("123.0") + + -8.1 + |> float.to_string + |> should.equal("-8.1") + } + + pub fn clamp_test() { + float.clamp(1.4, min: 1.3, max: 1.5) + |> should.equal(1.4) + + float.clamp(1.2, min: 1.3, max: 1.5) + |> should.equal(1.3) + + float.clamp(1.6, min: 1.3, max: 1.5) + |> should.equal(1.5) + } + + pub fn compare_test() { + float.compare(0., 0.) + |> should.equal(order.Eq) + + float.compare(0.1, 0.1) + |> should.equal(order.Eq) + + float.compare(0., 0.1) + |> should.equal(order.Lt) + + float.compare(-2., -1.9) + |> should.equal(order.Lt) + + float.compare(2., 1.9) + |> should.equal(order.Gt) + + float.compare(-1.9, -2.) + |> should.equal(order.Gt) + } + + pub fn ceiling_test() { + 8.1 + |> float.ceiling + |> should.equal(9.0) + + -8.1 + |> float.ceiling + |> should.equal(-8.0) + + -8.0 + |> float.ceiling + |> should.equal(-8.0) + } -pub fn to_string_test() { - 123.0 - |> float.to_string - |> should.equal("123.0") + pub fn floor_test() { + 8.1 + |> float.floor + |> should.equal(8.0) - -8.1 - |> float.to_string - |> should.equal("-8.1") -} + -8.1 + |> float.floor + |> should.equal(-9.0) -pub fn clamp_test() { - float.clamp(1.4, min: 1.3, max: 1.5) - |> should.equal(1.4) + -8.0 + |> float.floor + |> should.equal(-8.0) + } - float.clamp(1.2, min: 1.3, max: 1.5) - |> should.equal(1.3) + pub fn round_test() { + 8.1 + |> float.round + |> should.equal(8) - float.clamp(1.6, min: 1.3, max: 1.5) - |> should.equal(1.5) -} + 8.4 + |> float.round + |> should.equal(8) -pub fn compare_test() { - float.compare(0., 0.) - |> should.equal(order.Eq) + 8.499 + |> float.round + |> should.equal(8) - float.compare(0.1, 0.1) - |> should.equal(order.Eq) + 8.5 + |> float.round + |> should.equal(9) - float.compare(0., 0.1) - |> should.equal(order.Lt) + -8.1 + |> float.round + |> should.equal(-8) - float.compare(-2., -1.9) - |> should.equal(order.Lt) + -7.5 + |> float.round + |> should.equal(-8) + } - float.compare(2., 1.9) - |> should.equal(order.Gt) + pub fn truncate_test() { + 8.1 + |> float.truncate + |> should.equal(8) - float.compare(-1.9, -2.) - |> should.equal(order.Gt) -} + 8.4 + |> float.truncate + |> should.equal(8) -pub fn ceiling_test() { - 8.1 - |> float.ceiling - |> should.equal(9.0) + 8.499 + |> float.truncate + |> should.equal(8) - -8.1 - |> float.ceiling - |> should.equal(-8.0) + 8.5 + |> float.truncate + |> should.equal(8) - -8.0 - |> float.ceiling - |> should.equal(-8.0) -} + -8.1 + |> float.truncate + |> should.equal(-8) -pub fn floor_test() { - 8.1 - |> float.floor - |> should.equal(8.0) + -7.5 + |> float.truncate + |> should.equal(-7) + } - -8.1 - |> float.floor - |> should.equal(-9.0) + pub fn min_test() { + float.min(0., 0.) + |> should.equal(0.) - -8.0 - |> float.floor - |> should.equal(-8.0) -} + float.min(0.3, 1.5) + |> should.equal(0.3) -pub fn round_test() { - 8.1 - |> float.round - |> should.equal(8) + float.min(1., 0.) + |> should.equal(0.) - 8.4 - |> float.round - |> should.equal(8) + float.min(-1.7, 2.5) + |> should.equal(-1.7) - 8.499 - |> float.round - |> should.equal(8) + float.min(-2.2, -2.2) + |> should.equal(-2.2) - 8.5 - |> float.round - |> should.equal(9) + float.min(-1., -1.) + |> should.equal(-1.) - -8.1 - |> float.round - |> should.equal(-8) + float.min(-1.1, -1.) + |> should.equal(-1.1) + } - -7.5 - |> float.round - |> should.equal(-8) -} + pub fn max_test() { + float.max(0., 0.) + |> should.equal(0.) -pub fn truncate_test() { - 8.1 - |> float.truncate - |> should.equal(8) + float.max(0.3, 1.5) + |> should.equal(1.5) - 8.4 - |> float.truncate - |> should.equal(8) + float.max(1., 0.) + |> should.equal(1.) - 8.499 - |> float.truncate - |> should.equal(8) + float.max(-1.7, 2.5) + |> should.equal(2.5) - 8.5 - |> float.truncate - |> should.equal(8) + float.max(-2.2, -2.2) + |> should.equal(-2.2) - -8.1 - |> float.truncate - |> should.equal(-8) + float.max(-1., -1.) + |> should.equal(-1.) - -7.5 - |> float.truncate - |> should.equal(-7) -} + float.max(-1.1, -1.) + |> should.equal(-1.) + } -pub fn min_test() { - float.min(0., 0.) - |> should.equal(0.) + pub fn absolute_value_test() { + float.absolute_value(-1.0) + |> should.equal(1.0) - float.min(0.3, 1.5) - |> should.equal(0.3) + float.absolute_value(-20.6) + |> should.equal(20.6) - float.min(1., 0.) - |> should.equal(0.) + float.absolute_value(0.0) + |> should.equal(0.0) - float.min(-1.7, 2.5) - |> should.equal(-1.7) + float.absolute_value(1.0) + |> should.equal(1.0) - float.min(-2.2, -2.2) - |> should.equal(-2.2) + float.absolute_value(25.2) + |> should.equal(25.2) + } - float.min(-1., -1.) - |> should.equal(-1.) + pub fn power_test() { + float.power(2.0, 2.0) + |> should.equal(4.0) - float.min(-1.1, -1.) - |> should.equal(-1.1) -} - -pub fn max_test() { - float.max(0., 0.) - |> should.equal(0.) + float.power(-5.0, 3.0) + |> should.equal(-125.0) - float.max(0.3, 1.5) - |> should.equal(1.5) + float.power(10.5, 0.0) + |> should.equal(1.0) - float.max(1., 0.) - |> should.equal(1.) + float.power(16.0, 0.5) + |> should.equal(4.0) - float.max(-1.7, 2.5) - |> should.equal(2.5) + float.power(2.0, -1.0) + |> should.equal(0.5) + } - float.max(-2.2, -2.2) - |> should.equal(-2.2) + pub fn square_root_test() { + float.square_root(4.0) + |> should.equal(Ok(2.0)) - float.max(-1., -1.) - |> should.equal(-1.) - - float.max(-1.1, -1.) - |> should.equal(-1.) -} + float.square_root(16.0) + |> should.equal(Ok(4.0)) -pub fn absolute_value_test() { - float.absolute_value(-1.0) - |> should.equal(1.0) + float.square_root(0.0) + |> should.equal(Ok(0.0)) - float.absolute_value(-20.6) - |> should.equal(20.6) + float.square_root(-4.0) + |> should.equal(Error(Nil)) + } - float.absolute_value(0.0) - |> should.equal(0.0) - - float.absolute_value(1.0) - |> should.equal(1.0) - - float.absolute_value(25.2) - |> should.equal(25.2) -} + pub fn negate_test() { + float.negate(-1.) + |> should.equal(1.) -pub fn power_test() { - float.power(2.0, 2.0) - |> should.equal(4.0) + float.negate(2.) + |> should.equal(-2.) - float.power(-5.0, 3.0) - |> should.equal(-125.0) + float.negate(0.) + |> should.equal(0.) + } - float.power(10.5, 0.0) - |> should.equal(1.0) + pub fn sum_test() { + float.sum([]) + |> should.equal(0.0) - float.power(16.0, 0.5) - |> should.equal(4.0) - - float.power(2.0, -1.0) - |> should.equal(0.5) -} - -pub fn square_root_test() { - float.square_root(4.0) - |> should.equal(Ok(2.0)) - - float.square_root(16.0) - |> should.equal(Ok(4.0)) - - float.square_root(0.0) - |> should.equal(Ok(0.0)) - - float.square_root(-4.0) - |> should.equal(Error(Nil)) -} - -pub fn negate_test() { - float.negate(-1.) - |> should.equal(1.) - - float.negate(2.) - |> should.equal(-2.) - - float.negate(0.) - |> should.equal(0.) -} - -pub fn sum_test() { - float.sum([]) - |> should.equal(0.0) - - float.sum([1.0, 2.2, 3.3]) - |> should.equal(6.5) -} + float.sum([1.0, 2.2, 3.3]) + |> should.equal(6.5) + } -pub fn product_test() { - float.product([]) - |> should.equal(0.) + pub fn product_test() { + float.product([]) + |> should.equal(0.) - float.product([4.]) - |> should.equal(4.) + float.product([4.]) + |> should.equal(4.) - float.product([2.5, 3.2, 4.2]) - |> should.equal(33.6) + float.product([2.5, 3.2, 4.2]) + |> should.equal(33.6) + } } diff --git a/test/gleam/function_test.gleam b/test/gleam/function_test.gleam index 721d10d..ec65f63 100644 --- a/test/gleam/function_test.gleam +++ b/test/gleam/function_test.gleam @@ -1,126 +1,128 @@ -import gleam/should -import gleam/dynamic -import gleam/function -import gleam/int -import gleam/list -import gleam/result -import gleam/string - -pub fn compose_test() { - let add_two = fn(int: Int) { int + 2 } - let add_three = fn(int: Int) { int + 3 } - - let add_five = function.compose(add_two, add_three) - - 1 - |> add_five - |> should.equal(6) - - // Takes a list of ints and returns the head as a string (if there is one, or - // else "0" if there is not) - let head_to_string = - list.head - |> function.compose(result.unwrap(_, 0)) - |> function.compose(int.to_string) - - [1] - |> head_to_string - |> should.equal("1") - - [] - |> head_to_string - |> should.equal("0") -} +if erlang { + import gleam/should + import gleam/dynamic + import gleam/function + import gleam/int + import gleam/list + import gleam/result + import gleam/string + + pub fn compose_test() { + let add_two = fn(int: Int) { int + 2 } + let add_three = fn(int: Int) { int + 3 } + + let add_five = function.compose(add_two, add_three) + + 1 + |> add_five + |> should.equal(6) + + // Takes a list of ints and returns the head as a string (if there is one, or + // else "0" if there is not) + let head_to_string = + list.head + |> function.compose(result.unwrap(_, 0)) + |> function.compose(int.to_string) + + [1] + |> head_to_string + |> should.equal("1") + + [] + |> head_to_string + |> should.equal("0") + } -pub fn curry2_test() { - let fun = fn(a, b) { a + b } - let curried = function.curry2(fun) + pub fn curry2_test() { + let fun = fn(a, b) { a + b } + let curried = function.curry2(fun) - curried(1)(2) - |> should.equal(3) -} + curried(1)(2) + |> should.equal(3) + } -pub fn curry3_test() { - let fun = fn(a, b, c) { a + b + c } - let curried = function.curry3(fun) + pub fn curry3_test() { + let fun = fn(a, b, c) { a + b + c } + let curried = function.curry3(fun) - curried(1)(2)(4) - |> should.equal(7) -} - -pub fn curry4_test() { - let fun = fn(a, b, c, d) { a + b + c + d } - let curried = function.curry4(fun) + curried(1)(2)(4) + |> should.equal(7) + } - curried(1)(2)(4)(8) - |> should.equal(15) -} + pub fn curry4_test() { + let fun = fn(a, b, c, d) { a + b + c + d } + let curried = function.curry4(fun) -pub fn curry5_test() { - let fun = fn(a, b, c, d, e) { a + b + c + d + e } - let curried = function.curry5(fun) + curried(1)(2)(4)(8) + |> should.equal(15) + } - curried(1)(2)(4)(8)(16) - |> should.equal(31) -} + pub fn curry5_test() { + let fun = fn(a, b, c, d, e) { a + b + c + d + e } + let curried = function.curry5(fun) -pub fn curry6_test() { - let fun = fn(a, b, c, d, e, f) { a + b + c + d + e + f } - let curried = function.curry6(fun) + curried(1)(2)(4)(8)(16) + |> should.equal(31) + } - curried(1)(2)(4)(8)(16)(32) - |> should.equal(63) -} + pub fn curry6_test() { + let fun = fn(a, b, c, d, e, f) { a + b + c + d + e + f } + let curried = function.curry6(fun) -pub fn flip_test() { - let fun = fn(s: String, i: Int) { - s - |> string.append("String: '", _) - |> string.append("', Int: '") - |> string.append(int.to_string(i)) - |> string.append("'") + curried(1)(2)(4)(8)(16)(32) + |> should.equal(63) } - let flipped_fun = function.flip(fun) + pub fn flip_test() { + let fun = fn(s: String, i: Int) { + s + |> string.append("String: '", _) + |> string.append("', Int: '") + |> string.append(int.to_string(i)) + |> string.append("'") + } - fun("Bob", 1) - |> should.equal("String: 'Bob', Int: '1'") + let flipped_fun = function.flip(fun) - flipped_fun(2, "Alice") - |> should.equal("String: 'Alice', Int: '2'") -} + fun("Bob", 1) + |> should.equal("String: 'Bob', Int: '1'") -pub fn identity_test() { - 1 - |> function.identity - |> should.equal(1) + flipped_fun(2, "Alice") + |> should.equal("String: 'Alice', Int: '2'") + } - "" - |> function.identity - |> should.equal("") + pub fn identity_test() { + 1 + |> function.identity + |> should.equal(1) - [] - |> function.identity - |> should.equal([]) + "" + |> function.identity + |> should.equal("") - #(1, 2.0) - |> function.identity - |> should.equal(#(1, 2.0)) -} + [] + |> function.identity + |> should.equal([]) + + #(1, 2.0) + |> function.identity + |> should.equal(#(1, 2.0)) + } -external fn throw(a) -> Nil = - "erlang" "throw" + external fn throw(a) -> Nil = + "erlang" "throw" -external fn raise_error(a) -> Nil = - "erlang" "error" + external fn raise_error(a) -> Nil = + "erlang" "error" -pub fn rescue_test() { - function.rescue(fn() { 1 }) - |> should.equal(Ok(1)) + pub fn rescue_test() { + function.rescue(fn() { 1 }) + |> should.equal(Ok(1)) - function.rescue(fn() { throw(1) }) - |> should.equal(Error(function.Thrown(dynamic.from(1)))) + function.rescue(fn() { throw(1) }) + |> should.equal(Error(function.Thrown(dynamic.from(1)))) - function.rescue(fn() { raise_error("") }) - |> should.equal(Error(function.Errored(dynamic.from("")))) + function.rescue(fn() { raise_error("") }) + |> should.equal(Error(function.Errored(dynamic.from("")))) + } } diff --git a/test/gleam/int_test.gleam b/test/gleam/int_test.gleam index bec9624..31d06de 100644 --- a/test/gleam/int_test.gleam +++ b/test/gleam/int_test.gleam @@ -1,228 +1,230 @@ -import gleam/should -import gleam/int -import gleam/order - -pub fn absolute_value_test() { - 123 - |> int.absolute_value - |> should.equal(123) - - -123 - |> int.absolute_value - |> should.equal(123) -} +if erlang { + import gleam/should + import gleam/int + import gleam/order + + pub fn absolute_value_test() { + 123 + |> int.absolute_value + |> should.equal(123) -pub fn clamp_test() { - int.clamp(40, min: 30, max: 50) - |> should.equal(40) + -123 + |> int.absolute_value + |> should.equal(123) + } - int.clamp(20, min: 30, max: 50) - |> should.equal(30) + pub fn clamp_test() { + int.clamp(40, min: 30, max: 50) + |> should.equal(40) - int.clamp(60, min: 30, max: 50) - |> should.equal(50) + int.clamp(20, min: 30, max: 50) + |> should.equal(30) - // If the bounds are reversed we return the min - int.clamp(100, min: 50, max: 30) - |> should.equal(50) -} + int.clamp(60, min: 30, max: 50) + |> should.equal(50) -pub fn to_string_test() { - 123 - |> int.to_string - |> should.equal("123") + // If the bounds are reversed we return the min + int.clamp(100, min: 50, max: 30) + |> should.equal(50) + } - -123 - |> int.to_string - |> should.equal("-123") + pub fn to_string_test() { + 123 + |> int.to_string + |> should.equal("123") - 123 - |> int.to_string - |> should.equal("123") -} + -123 + |> int.to_string + |> should.equal("-123") -pub fn parse_test() { - "123" - |> int.parse - |> should.equal(Ok(123)) + 123 + |> int.to_string + |> should.equal("123") + } - "-123" - |> int.parse - |> should.equal(Ok(-123)) + pub fn parse_test() { + "123" + |> int.parse + |> should.equal(Ok(123)) - "0123" - |> int.parse - |> should.equal(Ok(123)) + "-123" + |> int.parse + |> should.equal(Ok(-123)) - "" - |> int.parse - |> should.equal(Error(Nil)) + "0123" + |> int.parse + |> should.equal(Ok(123)) - "what" - |> int.parse - |> should.equal(Error(Nil)) + "" + |> int.parse + |> should.equal(Error(Nil)) - "1.23" - |> int.parse - |> should.equal(Error(Nil)) -} + "what" + |> int.parse + |> should.equal(Error(Nil)) -pub fn to_base_string_test() { - 100 - |> int.to_base_string(16) - |> should.equal("64") + "1.23" + |> int.parse + |> should.equal(Error(Nil)) + } - -100 - |> int.to_base_string(16) - |> should.equal("-64") -} + pub fn to_base_string_test() { + 100 + |> int.to_base_string(16) + |> should.equal("64") -pub fn to_float_test() { - int.to_float(1) - |> should.equal(1.) + -100 + |> int.to_base_string(16) + |> should.equal("-64") + } - int.to_float(5) - |> should.equal(5.) + pub fn to_float_test() { + int.to_float(1) + |> should.equal(1.) - int.to_float(0) - |> should.equal(0.) + int.to_float(5) + |> should.equal(5.) - int.to_float(-5) - |> should.equal(-5.) -} + int.to_float(0) + |> should.equal(0.) -pub fn compare_test() { - int.compare(0, 0) - |> should.equal(order.Eq) + int.to_float(-5) + |> should.equal(-5.) + } - int.compare(1, 1) - |> should.equal(order.Eq) + pub fn compare_test() { + int.compare(0, 0) + |> should.equal(order.Eq) - int.compare(0, 1) - |> should.equal(order.Lt) + int.compare(1, 1) + |> should.equal(order.Eq) - int.compare(-2, -1) - |> should.equal(order.Lt) + int.compare(0, 1) + |> should.equal(order.Lt) - int.compare(2, 1) - |> should.equal(order.Gt) + int.compare(-2, -1) + |> should.equal(order.Lt) - int.compare(-1, -2) - |> should.equal(order.Gt) -} + int.compare(2, 1) + |> should.equal(order.Gt) -pub fn min_test() { - int.min(0, 0) - |> should.equal(0) + int.compare(-1, -2) + |> should.equal(order.Gt) + } - int.min(0, 1) - |> should.equal(0) + pub fn min_test() { + int.min(0, 0) + |> should.equal(0) - int.min(1, 0) - |> should.equal(0) + int.min(0, 1) + |> should.equal(0) - int.min(-1, 2) - |> should.equal(-1) + int.min(1, 0) + |> should.equal(0) - int.min(2, -2) - |> should.equal(-2) + int.min(-1, 2) + |> should.equal(-1) - int.min(-1, -1) - |> should.equal(-1) -} + int.min(2, -2) + |> should.equal(-2) -pub fn max_test() { - int.max(0, 0) - |> should.equal(0) + int.min(-1, -1) + |> should.equal(-1) + } - int.max(0, 1) - |> should.equal(1) + pub fn max_test() { + int.max(0, 0) + |> should.equal(0) - int.max(1, 0) - |> should.equal(1) + int.max(0, 1) + |> should.equal(1) - int.max(-1, 2) - |> should.equal(2) + int.max(1, 0) + |> should.equal(1) - int.max(2, -2) - |> should.equal(2) + int.max(-1, 2) + |> should.equal(2) - int.max(-1, -1) - |> should.equal(-1) -} + int.max(2, -2) + |> should.equal(2) -pub fn is_even_test() { - int.is_even(0) - |> should.be_true + int.max(-1, -1) + |> should.equal(-1) + } - int.is_even(2) - |> should.be_true + pub fn is_even_test() { + int.is_even(0) + |> should.be_true - int.is_even(-2) - |> should.be_true + int.is_even(2) + |> should.be_true - int.is_even(10006) - |> should.be_true + int.is_even(-2) + |> should.be_true - int.is_even(1) - |> should.be_false + int.is_even(10006) + |> should.be_true - int.is_even(-3) - |> should.be_false + int.is_even(1) + |> should.be_false - int.is_even(10005) - |> should.be_false -} + int.is_even(-3) + |> should.be_false -pub fn is_odd_test() { - int.is_odd(0) - |> should.be_false + int.is_even(10005) + |> should.be_false + } - int.is_odd(2) - |> should.be_false + pub fn is_odd_test() { + int.is_odd(0) + |> should.be_false - int.is_odd(-2) - |> should.be_false + int.is_odd(2) + |> should.be_false - int.is_odd(10006) - |> should.be_false + int.is_odd(-2) + |> should.be_false - int.is_odd(1) - |> should.be_true + int.is_odd(10006) + |> should.be_false - int.is_odd(-3) - |> should.be_true + int.is_odd(1) + |> should.be_true - int.is_odd(10005) - |> should.be_true -} + int.is_odd(-3) + |> should.be_true -pub fn negate_test() { - int.negate(-1) - |> should.equal(1) + int.is_odd(10005) + |> should.be_true + } - int.negate(2) - |> should.equal(-2) + pub fn negate_test() { + int.negate(-1) + |> should.equal(1) - int.negate(0) - |> should.equal(0) -} + int.negate(2) + |> should.equal(-2) -pub fn sum_test() { - int.sum([]) - |> should.equal(0) + int.negate(0) + |> should.equal(0) + } - int.sum([1, 2, 3]) - |> should.equal(6) -} + pub fn sum_test() { + int.sum([]) + |> should.equal(0) + + int.sum([1, 2, 3]) + |> should.equal(6) + } -pub fn product_test() { - int.product([]) - |> should.equal(0) + pub fn product_test() { + int.product([]) + |> should.equal(0) - int.product([4]) - |> should.equal(4) + int.product([4]) + |> should.equal(4) - int.product([1, 2, 3]) - |> should.equal(6) + int.product([1, 2, 3]) + |> should.equal(6) + } } diff --git a/test/gleam/iterator_test.gleam b/test/gleam/iterator_test.gleam index 0fb7143..0c0176f 100644 --- a/test/gleam/iterator_test.gleam +++ b/test/gleam/iterator_test.gleam @@ -1,421 +1,423 @@ -import gleam/should -import gleam/iterator.{Done, Next} -import gleam/list -import gleam/map - -// a |> from_list |> to_list == a -pub fn to_from_list_test() { - let test = fn(subject) { - subject - |> iterator.from_list - |> iterator.to_list - |> should.equal(subject) +if erlang { + import gleam/should + import gleam/iterator.{Done, Next} + import gleam/list + import gleam/map + + // a |> from_list |> to_list == a + pub fn to_from_list_test() { + let test = fn(subject) { + subject + |> iterator.from_list + |> iterator.to_list + |> should.equal(subject) + } + + test([]) + test([1]) + test([1, 2]) + test([1, 2, 4, 8]) } - test([]) - test([1]) - test([1, 2]) - test([1, 2, 4, 8]) -} + pub fn step_test() { + let test = fn(subject) { + let step = + subject + |> iterator.from_list + |> iterator.step + + case subject { + [] -> + step + |> should.equal(Done) + + [h, ..t] -> + step + |> should.equal(Next(h, iterator.from_list(t))) + } + } -pub fn step_test() { - let test = fn(subject) { - let step = + test([]) + test([1]) + test([1, 2]) + test([1, 2, 3]) + } + + // a |> from_list |> take(n) == a |> list.take(_, n) + pub fn take_test() { + let test = fn(n, subject) { subject |> iterator.from_list - |> iterator.step + |> iterator.take(n) + |> iterator.to_list + |> should.equal(list.take(subject, n)) + } - case subject { - [] -> - step - |> should.equal(Done) + test(0, []) + test(1, []) + test(-1, []) + test(0, [0]) + test(1, [0]) + test(-1, [0]) + test(0, [0, 1, 2, 3, 4]) + test(1, [0, 1, 2, 3, 4]) + test(2, [0, 1, 2, 3, 4]) + test(22, [0, 1, 2, 3, 4]) + } - [h, ..t] -> - step - |> should.equal(Next(h, iterator.from_list(t))) + // a |> from_list |> fold(a, f) == a |> list.fold(_, a, f) + pub fn fold_test() { + let test = fn(subject, acc, f) { + subject + |> iterator.from_list + |> iterator.fold(acc, f) + |> should.equal(list.fold(subject, acc, f)) } - } - test([]) - test([1]) - test([1, 2]) - test([1, 2, 3]) -} + let f = fn(e, acc) { [e, ..acc] } + test([], [], f) + test([1], [], f) + test([1, 2, 3], [], f) + test([1, 2, 3, 4, 5, 6, 7, 8], [], f) + } -// a |> from_list |> take(n) == a |> list.take(_, n) -pub fn take_test() { - let test = fn(n, subject) { - subject - |> iterator.from_list - |> iterator.take(n) - |> iterator.to_list - |> should.equal(list.take(subject, n)) - } - - test(0, []) - test(1, []) - test(-1, []) - test(0, [0]) - test(1, [0]) - test(-1, [0]) - test(0, [0, 1, 2, 3, 4]) - test(1, [0, 1, 2, 3, 4]) - test(2, [0, 1, 2, 3, 4]) - test(22, [0, 1, 2, 3, 4]) -} + // a |> from_list |> map(f) |> to_list == a |> list.map(_, f) + pub fn map_test() { + let test = fn(subject, f) { + subject + |> iterator.from_list + |> iterator.map(f) + |> iterator.to_list + |> should.equal(list.map(subject, f)) + } -// a |> from_list |> fold(a, f) == a |> list.fold(_, a, f) -pub fn fold_test() { - let test = fn(subject, acc, f) { - subject - |> iterator.from_list - |> iterator.fold(acc, f) - |> should.equal(list.fold(subject, acc, f)) + let f = fn(e) { e * 2 } + test([], f) + test([1], f) + test([1, 2, 3], f) + test([1, 2, 3, 4, 5, 6, 7, 8], f) } - let f = fn(e, acc) { [e, ..acc] } - test([], [], f) - test([1], [], f) - test([1, 2, 3], [], f) - test([1, 2, 3, 4, 5, 6, 7, 8], [], f) -} + // a |> from_list |> flat_map(f) |> to_list == + // a |> list.map(f) |> list.map(to_list) |> list.flatten + pub fn flat_map_test() { + let test = fn(subject, f) { + subject + |> iterator.from_list + |> iterator.flat_map(f) + |> iterator.to_list + |> should.equal( + subject + |> list.map(f) + |> list.map(iterator.to_list) + |> list.flatten, + ) + } -// a |> from_list |> map(f) |> to_list == a |> list.map(_, f) -pub fn map_test() { - let test = fn(subject, f) { - subject - |> iterator.from_list - |> iterator.map(f) - |> iterator.to_list - |> should.equal(list.map(subject, f)) + let f = fn(i) { iterator.range(i, i + 2) } + + test([], f) + test([1], f) + test([1, 2], f) } - let f = fn(e) { e * 2 } - test([], f) - test([1], f) - test([1, 2, 3], f) - test([1, 2, 3, 4, 5, 6, 7, 8], f) -} + // a |> from_list |> append(from_list(b)) |> to_list == list.flatten([a, b]) + pub fn append_test() { + let test = fn(left, right) { + left + |> iterator.from_list + |> iterator.append(iterator.from_list(right)) + |> iterator.to_list + |> should.equal(list.flatten([left, right])) + } -// a |> from_list |> flat_map(f) |> to_list == -// a |> list.map(f) |> list.map(to_list) |> list.flatten -pub fn flat_map_test() { - let test = fn(subject, f) { - subject - |> iterator.from_list - |> iterator.flat_map(f) - |> iterator.to_list - |> should.equal( - subject - |> list.map(f) - |> list.map(iterator.to_list) - |> list.flatten, - ) + test([], []) + test([1], [2]) + test([1, 2], [3, 4]) } - let f = fn(i) { iterator.range(i, i + 2) } - - test([], f) - test([1], f) - test([1, 2], f) -} + // a |> list.map(from_list) |> flatten |> to_list == list.flatten(a) + pub fn flatten_test() { + let test = fn(lists) { + lists + |> list.map(iterator.from_list) + |> iterator.from_list + |> iterator.flatten + |> iterator.to_list + |> should.equal(list.flatten(lists)) + } -// a |> from_list |> append(from_list(b)) |> to_list == list.flatten([a, b]) -pub fn append_test() { - let test = fn(left, right) { - left - |> iterator.from_list - |> iterator.append(iterator.from_list(right)) - |> iterator.to_list - |> should.equal(list.flatten([left, right])) + test([[], []]) + test([[1], [2]]) + test([[1, 2], [3, 4]]) } - test([], []) - test([1], [2]) - test([1, 2], [3, 4]) -} + // a |> from_list |> filter(f) |> to_list == a |> list.filter(_, f) + pub fn filter_test() { + let test = fn(subject, f) { + subject + |> iterator.from_list + |> iterator.filter(f) + |> iterator.to_list + |> should.equal(list.filter(subject, f)) + } -// a |> list.map(from_list) |> flatten |> to_list == list.flatten(a) -pub fn flatten_test() { - let test = fn(lists) { - lists - |> list.map(iterator.from_list) - |> iterator.from_list - |> iterator.flatten - |> iterator.to_list - |> should.equal(list.flatten(lists)) + let even = fn(x) { x % 2 == 0 } + test([], even) + test([1], even) + test([1, 2], even) + test([1, 2, 3], even) + test([1, 2, 3, 4], even) + test([1, 2, 3, 4, 5], even) + test([1, 2, 3, 4, 5, 6], even) } - test([[], []]) - test([[1], [2]]) - test([[1, 2], [3, 4]]) -} + pub fn repeat_test() { + 1 + |> iterator.repeat + |> iterator.take(5) + |> iterator.to_list + |> should.equal([1, 1, 1, 1, 1]) + } -// a |> from_list |> filter(f) |> to_list == a |> list.filter(_, f) -pub fn filter_test() { - let test = fn(subject, f) { - subject + pub fn cycle_test() { + [1, 2, 3] |> iterator.from_list - |> iterator.filter(f) + |> iterator.cycle + |> iterator.take(9) |> iterator.to_list - |> should.equal(list.filter(subject, f)) + |> should.equal([1, 2, 3, 1, 2, 3, 1, 2, 3]) } - let even = fn(x) { x % 2 == 0 } - test([], even) - test([1], even) - test([1, 2], even) - test([1, 2, 3], even) - test([1, 2, 3, 4], even) - test([1, 2, 3, 4, 5], even) - test([1, 2, 3, 4, 5, 6], even) -} + pub fn unfold_test() { + iterator.unfold(2, fn(acc) { iterator.Next(acc, acc * 2) }) + |> iterator.take(5) + |> iterator.to_list + |> should.equal([2, 4, 8, 16, 32]) -pub fn repeat_test() { - 1 - |> iterator.repeat - |> iterator.take(5) - |> iterator.to_list - |> should.equal([1, 1, 1, 1, 1]) -} + iterator.unfold(2, fn(_) { iterator.Done }) + |> iterator.take(5) + |> iterator.to_list + |> should.equal([]) -pub fn cycle_test() { - [1, 2, 3] - |> iterator.from_list - |> iterator.cycle - |> iterator.take(9) - |> iterator.to_list - |> should.equal([1, 2, 3, 1, 2, 3, 1, 2, 3]) -} + fn(n) { + case n { + 0 -> iterator.Done + n -> iterator.Next(element: n, accumulator: n - 1) + } + } + |> iterator.unfold(from: 5) + |> iterator.to_list + |> should.equal([5, 4, 3, 2, 1]) + } -pub fn unfold_test() { - iterator.unfold(2, fn(acc) { iterator.Next(acc, acc * 2) }) - |> iterator.take(5) - |> iterator.to_list - |> should.equal([2, 4, 8, 16, 32]) - - iterator.unfold(2, fn(_) { iterator.Done }) - |> iterator.take(5) - |> iterator.to_list - |> should.equal([]) - - fn(n) { - case n { - 0 -> iterator.Done - n -> iterator.Next(element: n, accumulator: n - 1) + pub fn range_test() { + let test = fn(a, b, expected) { + iterator.range(a, b) + |> iterator.to_list + |> should.equal(expected) } + + test(0, 0, []) + test(1, 1, []) + test(-1, -1, []) + test(0, 1, [0]) + test(0, 5, [0, 1, 2, 3, 4]) + test(1, -5, [1, 0, -1, -2, -3, -4]) } - |> iterator.unfold(from: 5) - |> iterator.to_list - |> should.equal([5, 4, 3, 2, 1]) -} -pub fn range_test() { - let test = fn(a, b, expected) { - iterator.range(a, b) + pub fn drop_test() { + iterator.range(0, 10) + |> iterator.drop(5) |> iterator.to_list - |> should.equal(expected) + |> should.equal([5, 6, 7, 8, 9]) } - test(0, 0, []) - test(1, 1, []) - test(-1, -1, []) - test(0, 1, [0]) - test(0, 5, [0, 1, 2, 3, 4]) - test(1, -5, [1, 0, -1, -2, -3, -4]) -} + type Cat { + Cat(id: Int) + } -pub fn drop_test() { - iterator.range(0, 10) - |> iterator.drop(5) - |> iterator.to_list - |> should.equal([5, 6, 7, 8, 9]) -} + pub fn find_test() { + iterator.range(0, 10) + |> iterator.find(fn(e) { e == 5 }) + |> should.equal(Ok(5)) -type Cat { - Cat(id: Int) -} + iterator.range(0, 10) + |> iterator.find(fn(e) { e > 10 }) + |> should.equal(Error(Nil)) -pub fn find_test() { - iterator.range(0, 10) - |> iterator.find(fn(e) { e == 5 }) - |> should.equal(Ok(5)) - - iterator.range(0, 10) - |> iterator.find(fn(e) { e > 10 }) - |> should.equal(Error(Nil)) - - iterator.empty() - |> iterator.find(fn(_x) { True }) - |> should.equal(Error(Nil)) - - iterator.unfold( - Cat(id: 1), - fn(cat: Cat) { iterator.Next(cat, Cat(id: cat.id + 1)) }, - ) - |> iterator.find(fn(cat: Cat) { cat.id == 10 }) - |> should.equal(Ok(Cat(id: 10))) -} + iterator.empty() + |> iterator.find(fn(_x) { True }) + |> should.equal(Error(Nil)) -pub fn index_test() { - iterator.from_list(["a", "b", "c"]) - |> iterator.index - |> iterator.to_list - |> should.equal([#(0, "a"), #(1, "b"), #(2, "c")]) -} + iterator.unfold( + Cat(id: 1), + fn(cat: Cat) { iterator.Next(cat, Cat(id: cat.id + 1)) }, + ) + |> iterator.find(fn(cat: Cat) { cat.id == 10 }) + |> should.equal(Ok(Cat(id: 10))) + } -pub fn iterate_test() { - fn(x) { x * 3 } - |> iterator.iterate(from: 1) - |> iterator.take(5) - |> iterator.to_list - |> should.equal([1, 3, 9, 27, 81]) -} + pub fn index_test() { + iterator.from_list(["a", "b", "c"]) + |> iterator.index + |> iterator.to_list + |> should.equal([#(0, "a"), #(1, "b"), #(2, "c")]) + } -pub fn take_while_test() { - iterator.from_list([1, 2, 3, 2, 4]) - |> iterator.take_while(satisfying: fn(x) { x < 3 }) - |> iterator.to_list - |> should.equal([1, 2]) -} + pub fn iterate_test() { + fn(x) { x * 3 } + |> iterator.iterate(from: 1) + |> iterator.take(5) + |> iterator.to_list + |> should.equal([1, 3, 9, 27, 81]) + } -pub fn drop_while_test() { - iterator.from_list([1, 2, 3, 4, 2, 5]) - |> iterator.drop_while(satisfying: fn(x) { x < 4 }) - |> iterator.to_list - |> should.equal([4, 2, 5]) -} + pub fn take_while_test() { + iterator.from_list([1, 2, 3, 2, 4]) + |> iterator.take_while(satisfying: fn(x) { x < 3 }) + |> iterator.to_list + |> should.equal([1, 2]) + } -pub fn scan_test() { - iterator.from_list([1, 2, 3, 4, 5]) - |> iterator.scan(from: 0, with: fn(el, acc) { acc + el }) - |> iterator.to_list - |> should.equal([1, 3, 6, 10, 15]) -} + pub fn drop_while_test() { + iterator.from_list([1, 2, 3, 4, 2, 5]) + |> iterator.drop_while(satisfying: fn(x) { x < 4 }) + |> iterator.to_list + |> should.equal([4, 2, 5]) + } -pub fn zip_test() { - iterator.from_list(["a", "b", "c"]) - |> iterator.zip(iterator.range(20, 30)) - |> iterator.to_list - |> should.equal([#("a", 20), #("b", 21), #("c", 22)]) -} + pub fn scan_test() { + iterator.from_list([1, 2, 3, 4, 5]) + |> iterator.scan(from: 0, with: fn(el, acc) { acc + el }) + |> iterator.to_list + |> should.equal([1, 3, 6, 10, 15]) + } -pub fn chunk_test() { - iterator.from_list([1, 2, 2, 3, 4, 4, 6, 7, 7]) - |> iterator.chunk(by: fn(n) { n % 2 }) - |> iterator.to_list - |> should.equal([[1], [2, 2], [3], [4, 4, 6], [7, 7]]) -} + pub fn zip_test() { + iterator.from_list(["a", "b", "c"]) + |> iterator.zip(iterator.range(20, 30)) + |> iterator.to_list + |> should.equal([#("a", 20), #("b", 21), #("c", 22)]) + } -pub fn sized_chunk_test() { - iterator.from_list([1, 2, 3, 4, 5, 6]) - |> iterator.sized_chunk(into: 2) - |> iterator.to_list - |> should.equal([[1, 2], [3, 4], [5, 6]]) + pub fn chunk_test() { + iterator.from_list([1, 2, 2, 3, 4, 4, 6, 7, 7]) + |> iterator.chunk(by: fn(n) { n % 2 }) + |> iterator.to_list + |> should.equal([[1], [2, 2], [3], [4, 4, 6], [7, 7]]) + } - iterator.from_list([1, 2, 3, 4, 5, 6, 7, 8]) - |> iterator.sized_chunk(into: 3) - |> iterator.to_list - |> should.equal([[1, 2, 3], [4, 5, 6], [7, 8]]) -} + pub fn sized_chunk_test() { + iterator.from_list([1, 2, 3, 4, 5, 6]) + |> iterator.sized_chunk(into: 2) + |> iterator.to_list + |> should.equal([[1, 2], [3, 4], [5, 6]]) -pub fn intersperse_test() { - iterator.empty() - |> iterator.intersperse(with: 0) - |> iterator.to_list - |> should.equal([]) - - iterator.from_list([1]) - |> iterator.intersperse(with: 0) - |> iterator.to_list - |> should.equal([1]) - - iterator.from_list([1, 2, 3, 4, 5]) - |> iterator.intersperse(with: 0) - |> iterator.to_list - |> should.equal([1, 0, 2, 0, 3, 0, 4, 0, 5]) -} + iterator.from_list([1, 2, 3, 4, 5, 6, 7, 8]) + |> iterator.sized_chunk(into: 3) + |> iterator.to_list + |> should.equal([[1, 2, 3], [4, 5, 6], [7, 8]]) + } -pub fn any_test() { - iterator.empty() - |> iterator.any(satisfying: fn(n) { n % 2 == 0 }) - |> should.be_false + pub fn intersperse_test() { + iterator.empty() + |> iterator.intersperse(with: 0) + |> iterator.to_list + |> should.equal([]) - iterator.from_list([1, 2, 5, 7, 9]) - |> iterator.any(satisfying: fn(n) { n % 2 == 0 }) - |> should.be_true + iterator.from_list([1]) + |> iterator.intersperse(with: 0) + |> iterator.to_list + |> should.equal([1]) - iterator.from_list([1, 3, 5, 7, 9]) - |> iterator.any(satisfying: fn(n) { n % 2 == 0 }) - |> should.be_false -} + iterator.from_list([1, 2, 3, 4, 5]) + |> iterator.intersperse(with: 0) + |> iterator.to_list + |> should.equal([1, 0, 2, 0, 3, 0, 4, 0, 5]) + } -pub fn all_test() { - iterator.empty() - |> iterator.all(satisfying: fn(n) { n % 2 == 0 }) - |> should.be_true + pub fn any_test() { + iterator.empty() + |> iterator.any(satisfying: fn(n) { n % 2 == 0 }) + |> should.be_false - iterator.from_list([2, 4, 6, 8]) - |> iterator.all(satisfying: fn(n) { n % 2 == 0 }) - |> should.be_true + iterator.from_list([1, 2, 5, 7, 9]) + |> iterator.any(satisfying: fn(n) { n % 2 == 0 }) + |> should.be_true - iterator.from_list([2, 4, 5, 8]) - |> iterator.all(satisfying: fn(n) { n % 2 == 0 }) - |> should.be_false -} + iterator.from_list([1, 3, 5, 7, 9]) + |> iterator.any(satisfying: fn(n) { n % 2 == 0 }) + |> should.be_false + } -pub fn group_test() { - iterator.from_list([1, 2, 3, 4, 5, 6]) - |> iterator.group(by: fn(n) { n % 3 }) - |> should.equal(map.from_list([#(0, [3, 6]), #(1, [1, 4]), #(2, [2, 5])])) -} + pub fn all_test() { + iterator.empty() + |> iterator.all(satisfying: fn(n) { n % 2 == 0 }) + |> should.be_true -pub fn reduce_test() { - iterator.empty() - |> iterator.reduce(with: fn(x, y) { x + y }) - |> should.equal(Error(Nil)) + iterator.from_list([2, 4, 6, 8]) + |> iterator.all(satisfying: fn(n) { n % 2 == 0 }) + |> should.be_true - iterator.from_list([1, 2, 3, 4, 5]) - |> iterator.reduce(with: fn(x, y) { x + y }) - |> should.equal(Ok(15)) -} + iterator.from_list([2, 4, 5, 8]) + |> iterator.all(satisfying: fn(n) { n % 2 == 0 }) + |> should.be_false + } -pub fn last_test() { - iterator.empty() - |> iterator.last - |> should.equal(Error(Nil)) + pub fn group_test() { + iterator.from_list([1, 2, 3, 4, 5, 6]) + |> iterator.group(by: fn(n) { n % 3 }) + |> should.equal(map.from_list([#(0, [3, 6]), #(1, [1, 4]), #(2, [2, 5])])) + } - iterator.range(1, 10) - |> iterator.last - |> should.equal(Ok(9)) -} + pub fn reduce_test() { + iterator.empty() + |> iterator.reduce(with: fn(x, y) { x + y }) + |> should.equal(Error(Nil)) -pub fn empty_test() { - iterator.empty() - |> iterator.to_list - |> should.equal([]) -} + iterator.from_list([1, 2, 3, 4, 5]) + |> iterator.reduce(with: fn(x, y) { x + y }) + |> should.equal(Ok(15)) + } -pub fn once_test() { - iterator.once(fn() { 1 }) - |> iterator.to_list - |> should.equal([1]) -} + pub fn last_test() { + iterator.empty() + |> iterator.last + |> should.equal(Error(Nil)) -pub fn single_test() { - iterator.single(1) - |> iterator.to_list - |> should.equal([1]) -} + iterator.range(1, 10) + |> iterator.last + |> should.equal(Ok(9)) + } + + pub fn empty_test() { + iterator.empty() + |> iterator.to_list + |> should.equal([]) + } -pub fn interleave_test() { - iterator.from_list([1, 2, 3, 4]) - |> iterator.interleave(with: iterator.from_list([11, 12, 13, 14])) - |> iterator.to_list - |> should.equal([1, 11, 2, 12, 3, 13, 4, 14]) + pub fn once_test() { + iterator.once(fn() { 1 }) + |> iterator.to_list + |> should.equal([1]) + } - iterator.from_list([1, 2, 3, 4]) - |> iterator.interleave(with: iterator.from_list([100])) - |> iterator.to_list - |> should.equal([1, 100, 2, 3, 4]) + pub fn single_test() { + iterator.single(1) + |> iterator.to_list + |> should.equal([1]) + } + + pub fn interleave_test() { + iterator.from_list([1, 2, 3, 4]) + |> iterator.interleave(with: iterator.from_list([11, 12, 13, 14])) + |> iterator.to_list + |> should.equal([1, 11, 2, 12, 3, 13, 4, 14]) + + iterator.from_list([1, 2, 3, 4]) + |> iterator.interleave(with: iterator.from_list([100])) + |> iterator.to_list + |> should.equal([1, 100, 2, 3, 4]) + } } diff --git a/test/gleam/list_test.gleam b/test/gleam/list_test.gleam index c4f5ea3..bfe2c0b 100644 --- a/test/gleam/list_test.gleam +++ b/test/gleam/list_test.gleam @@ -1,724 +1,726 @@ -import gleam/should -import gleam/list -import gleam/int -import gleam/float -import gleam/string -import gleam/pair - -pub fn length_test() { - list.length([]) - |> should.equal(0) - - list.length([1]) - |> should.equal(1) +if erlang { + import gleam/should + import gleam/list + import gleam/int + import gleam/float + import gleam/string + import gleam/pair + + pub fn length_test() { + list.length([]) + |> should.equal(0) + + list.length([1]) + |> should.equal(1) + + list.length([1, 1]) + |> should.equal(2) + + list.length([1, 1, 1]) + |> should.equal(3) + } - list.length([1, 1]) - |> should.equal(2) + pub fn reverse_test() { + list.reverse([]) + |> should.equal([]) + list.reverse([1, 2, 3, 4, 5]) + |> should.equal([5, 4, 3, 2, 1]) + } - list.length([1, 1, 1]) - |> should.equal(3) -} + pub fn is_empty_test() { + list.is_empty([]) + |> should.be_true + list.is_empty([1]) + |> should.be_false + } -pub fn reverse_test() { - list.reverse([]) - |> should.equal([]) - list.reverse([1, 2, 3, 4, 5]) - |> should.equal([5, 4, 3, 2, 1]) -} + pub fn contains_test() { + list.contains([0, 4, 5, 1], 1) + |> should.be_true + list.contains([0, 4, 5, 7], 1) + |> should.be_false + list.contains([], 1) + |> should.be_false + } -pub fn is_empty_test() { - list.is_empty([]) - |> should.be_true - list.is_empty([1]) - |> should.be_false -} + pub fn head_test() { + list.head([0, 4, 5, 7]) + |> should.equal(Ok(0)) -pub fn contains_test() { - list.contains([0, 4, 5, 1], 1) - |> should.be_true - list.contains([0, 4, 5, 7], 1) - |> should.be_false - list.contains([], 1) - |> should.be_false -} + list.head([]) + |> should.equal(Error(Nil)) + } -pub fn head_test() { - list.head([0, 4, 5, 7]) - |> should.equal(Ok(0)) + pub fn tail_test() { + list.tail([0, 4, 5, 7]) + |> should.equal(Ok([4, 5, 7])) - list.head([]) - |> should.equal(Error(Nil)) -} + list.tail([0]) + |> should.equal(Ok([])) -pub fn tail_test() { - list.tail([0, 4, 5, 7]) - |> should.equal(Ok([4, 5, 7])) + list.tail([]) + |> should.equal(Error(Nil)) + } - list.tail([0]) - |> should.equal(Ok([])) + pub fn filter_test() { + [] + |> list.filter(fn(_) { True }) + |> should.equal([]) - list.tail([]) - |> should.equal(Error(Nil)) -} + [0, 4, 5, 7, 3] + |> list.filter(fn(_) { True }) + |> should.equal([0, 4, 5, 7, 3]) -pub fn filter_test() { - [] - |> list.filter(fn(_) { True }) - |> should.equal([]) + [0, 4, 5, 7, 3] + |> list.filter(fn(x) { x > 4 }) + |> should.equal([5, 7]) - [0, 4, 5, 7, 3] - |> list.filter(fn(_) { True }) - |> should.equal([0, 4, 5, 7, 3]) + [0, 4, 5, 7, 3] + |> list.filter(fn(x) { x < 4 }) + |> should.equal([0, 3]) + } - [0, 4, 5, 7, 3] - |> list.filter(fn(x) { x > 4 }) - |> should.equal([5, 7]) + pub fn filter_map_test() { + [2, 4, 6, 1] + |> list.filter_map(fn(x) { Ok(x + 1) }) + |> should.equal([3, 5, 7, 2]) - [0, 4, 5, 7, 3] - |> list.filter(fn(x) { x < 4 }) - |> should.equal([0, 3]) -} + [2, 4, 6, 1] + |> list.filter_map(Error) + |> should.equal([]) + } -pub fn filter_map_test() { - [2, 4, 6, 1] - |> list.filter_map(fn(x) { Ok(x + 1) }) - |> should.equal([3, 5, 7, 2]) + pub fn map_test() { + [] + |> list.map(fn(x) { x * 2 }) + |> should.equal([]) - [2, 4, 6, 1] - |> list.filter_map(Error) - |> should.equal([]) -} + [0, 4, 5, 7, 3] + |> list.map(fn(x) { x * 2 }) + |> should.equal([0, 8, 10, 14, 6]) + } -pub fn map_test() { - [] - |> list.map(fn(x) { x * 2 }) - |> should.equal([]) + pub fn map_fold_test() { + [1, 2, 3, 4] + |> list.map_fold(from: 0, with: fn(i, acc) { #(i * 2, acc + i) }) + |> should.equal(#([2, 4, 6, 8], 10)) + } - [0, 4, 5, 7, 3] - |> list.map(fn(x) { x * 2 }) - |> should.equal([0, 8, 10, 14, 6]) -} + pub fn try_map_test() { + let fun = fn(x) { + case x == 6 || x == 5 || x == 4 { + True -> Ok(x * 2) + False -> Error(x) + } + } -pub fn map_fold_test() { - [1, 2, 3, 4] - |> list.map_fold(from: 0, with: fn(i, acc) { #(i * 2, acc + i) }) - |> should.equal(#([2, 4, 6, 8], 10)) -} + [5, 6, 5, 6] + |> list.try_map(fun) + |> should.equal(Ok([10, 12, 10, 12])) -pub fn try_map_test() { - let fun = fn(x) { - case x == 6 || x == 5 || x == 4 { - True -> Ok(x * 2) - False -> Error(x) - } + [4, 6, 5, 7, 3] + |> list.try_map(fun) + |> should.equal(Error(7)) } - [5, 6, 5, 6] - |> list.try_map(fun) - |> should.equal(Ok([10, 12, 10, 12])) - - [4, 6, 5, 7, 3] - |> list.try_map(fun) - |> should.equal(Error(7)) -} + pub fn drop_test() { + [] + |> list.drop(5) + |> should.equal([]) -pub fn drop_test() { - [] - |> list.drop(5) - |> should.equal([]) + [1, 2, 3, 4, 5, 6, 7, 8] + |> list.drop(5) + |> should.equal([6, 7, 8]) + } - [1, 2, 3, 4, 5, 6, 7, 8] - |> list.drop(5) - |> should.equal([6, 7, 8]) -} + pub fn take_test() { + [] + |> list.take(5) + |> should.equal([]) + [1, 2, 3, 4, 5, 6, 7, 8] + |> list.take(5) + |> should.equal([1, 2, 3, 4, 5]) + } -pub fn take_test() { - [] - |> list.take(5) - |> should.equal([]) - [1, 2, 3, 4, 5, 6, 7, 8] - |> list.take(5) - |> should.equal([1, 2, 3, 4, 5]) -} + pub fn new_test() { + list.new() + |> should.equal([]) + } -pub fn new_test() { - list.new() - |> should.equal([]) -} + pub fn append_test() { + list.append([1], [2, 3]) + |> should.equal([1, 2, 3]) + } -pub fn append_test() { - list.append([1], [2, 3]) - |> should.equal([1, 2, 3]) -} + pub fn flatten_test() { + list.flatten([]) + |> should.equal([]) -pub fn flatten_test() { - list.flatten([]) - |> should.equal([]) + list.flatten([[]]) + |> should.equal([]) - list.flatten([[]]) - |> should.equal([]) + list.flatten([[], [], []]) + |> should.equal([]) - list.flatten([[], [], []]) - |> should.equal([]) + list.flatten([[1, 2], [], [3, 4]]) + |> should.equal([1, 2, 3, 4]) + } - list.flatten([[1, 2], [], [3, 4]]) - |> should.equal([1, 2, 3, 4]) -} + pub fn flat_map_test() { + list.flat_map([1, 10, 20], fn(x) { [x, x + 1] }) + |> should.equal([1, 2, 10, 11, 20, 21]) + } -pub fn flat_map_test() { - list.flat_map([1, 10, 20], fn(x) { [x, x + 1] }) - |> should.equal([1, 2, 10, 11, 20, 21]) -} + pub fn fold_test() { + [1, 2, 3] + |> list.fold([], fn(x, acc) { [x, ..acc] }) + |> should.equal([3, 2, 1]) + } -pub fn fold_test() { - [1, 2, 3] - |> list.fold([], fn(x, acc) { [x, ..acc] }) - |> should.equal([3, 2, 1]) -} + pub fn fold_right_test() { + [1, 2, 3] + |> list.fold_right(from: [], with: fn(x, acc) { [x, ..acc] }) + |> should.equal([1, 2, 3]) + } -pub fn fold_right_test() { - [1, 2, 3] - |> list.fold_right(from: [], with: fn(x, acc) { [x, ..acc] }) - |> should.equal([1, 2, 3]) -} + pub fn index_fold_test() { + ["a", "b", "c"] + |> list.index_fold([], fn(ix, i, acc) { [#(ix, i), ..acc] }) + |> should.equal([#(2, "c"), #(1, "b"), #(0, "a")]) + } -pub fn index_fold_test() { - ["a", "b", "c"] - |> list.index_fold([], fn(ix, i, acc) { [#(ix, i), ..acc] }) - |> should.equal([#(2, "c"), #(1, "b"), #(0, "a")]) -} + pub fn fold_until_test() { + [1, 2, 3, 4] + |> list.fold_until( + from: 0, + with: fn(n, acc) { + case n < 4 { + True -> list.Continue(acc + n) + False -> list.Stop(acc) + } + }, + ) + |> should.equal(6) + } -pub fn fold_until_test() { - [1, 2, 3, 4] - |> list.fold_until( - from: 0, - with: fn(n, acc) { - case n < 4 { - True -> list.Continue(acc + n) - False -> list.Stop(acc) - } - }, - ) - |> should.equal(6) -} + pub fn try_fold_test() { + [1, 2, 3] + |> list.try_fold( + 0, + fn(i, acc) { + case i < 4 { + True -> Ok(acc + i) + False -> Error(Nil) + } + }, + ) + |> should.equal(Ok(6)) + + [1, 2, 3] + |> list.try_fold( + 0, + fn(i, acc) { + case i < 3 { + True -> Ok(acc + i) + False -> Error(Nil) + } + }, + ) + |> should.equal(Error(Nil)) + } -pub fn try_fold_test() { - [1, 2, 3] - |> list.try_fold( - 0, - fn(i, acc) { - case i < 4 { - True -> Ok(acc + i) - False -> Error(Nil) - } - }, - ) - |> should.equal(Ok(6)) - - [1, 2, 3] - |> list.try_fold( - 0, - fn(i, acc) { - case i < 3 { - True -> Ok(acc + i) - False -> Error(Nil) + pub fn find_map_test() { + let f = fn(x) { + case x { + 2 -> Ok(4) + _ -> Error(Nil) } - }, - ) - |> should.equal(Error(Nil)) -} - -pub fn find_map_test() { - let f = fn(x) { - case x { - 2 -> Ok(4) - _ -> Error(Nil) } - } - [1, 2, 3] - |> list.find_map(with: f) - |> should.equal(Ok(4)) + [1, 2, 3] + |> list.find_map(with: f) + |> should.equal(Ok(4)) - [1, 3, 2] - |> list.find_map(with: f) - |> should.equal(Ok(4)) + [1, 3, 2] + |> list.find_map(with: f) + |> should.equal(Ok(4)) - [1, 3] - |> list.find_map(with: f) - |> should.equal(Error(Nil)) -} + [1, 3] + |> list.find_map(with: f) + |> should.equal(Error(Nil)) + } -pub fn find_test() { - let is_two = fn(x) { x == 2 } + pub fn find_test() { + let is_two = fn(x) { x == 2 } - [1, 2, 3] - |> list.find(one_that: is_two) - |> should.equal(Ok(2)) + [1, 2, 3] + |> list.find(one_that: is_two) + |> should.equal(Ok(2)) - [1, 3, 2] - |> list.find(one_that: is_two) - |> should.equal(Ok(2)) + [1, 3, 2] + |> list.find(one_that: is_two) + |> should.equal(Ok(2)) - [1, 3] - |> list.find(one_that: is_two) - |> should.equal(Error(Nil)) -} + [1, 3] + |> list.find(one_that: is_two) + |> should.equal(Error(Nil)) + } -pub fn all_test() { - list.all([1, 2, 3, 4, 5], fn(x) { x > 0 }) - |> should.equal(True) + pub fn all_test() { + list.all([1, 2, 3, 4, 5], fn(x) { x > 0 }) + |> should.equal(True) - list.all([1, 2, 3, 4, 5], fn(x) { x < 0 }) - |> should.equal(False) + list.all([1, 2, 3, 4, 5], fn(x) { x < 0 }) + |> should.equal(False) - list.all([], fn(_) { False }) - |> should.equal(True) -} + list.all([], fn(_) { False }) + |> should.equal(True) + } -pub fn any_test() { - list.any([1, 2, 3, 4, 5], fn(x) { x == 2 }) - |> should.equal(True) + pub fn any_test() { + list.any([1, 2, 3, 4, 5], fn(x) { x == 2 }) + |> should.equal(True) - list.any([1, 2, 3, 4, 5], fn(x) { x < 0 }) - |> should.equal(False) + list.any([1, 2, 3, 4, 5], fn(x) { x < 0 }) + |> should.equal(False) - list.any([], fn(_) { False }) - |> should.equal(False) -} + list.any([], fn(_) { False }) + |> should.equal(False) + } -pub fn zip_test() { - list.zip([], [1, 2, 3]) - |> should.equal([]) + pub fn zip_test() { + list.zip([], [1, 2, 3]) + |> should.equal([]) - list.zip([1, 2], []) - |> should.equal([]) + list.zip([1, 2], []) + |> should.equal([]) - list.zip([1, 2, 3], [4, 5, 6]) - |> should.equal([#(1, 4), #(2, 5), #(3, 6)]) + list.zip([1, 2, 3], [4, 5, 6]) + |> should.equal([#(1, 4), #(2, 5), #(3, 6)]) - list.zip([5, 6], [1, 2, 3]) - |> should.equal([#(5, 1), #(6, 2)]) + list.zip([5, 6], [1, 2, 3]) + |> should.equal([#(5, 1), #(6, 2)]) - list.zip([5, 6, 7], [1, 2]) - |> should.equal([#(5, 1), #(6, 2)]) -} + list.zip([5, 6, 7], [1, 2]) + |> should.equal([#(5, 1), #(6, 2)]) + } -pub fn strict_zip_test() { - list.strict_zip([], [1, 2, 3]) - |> should.equal(Error(list.LengthMismatch)) + pub fn strict_zip_test() { + list.strict_zip([], [1, 2, 3]) + |> should.equal(Error(list.LengthMismatch)) - list.strict_zip([1, 2], []) - |> should.equal(Error(list.LengthMismatch)) + list.strict_zip([1, 2], []) + |> should.equal(Error(list.LengthMismatch)) - list.strict_zip([1, 2, 3], [4, 5, 6]) - |> should.equal(Ok([#(1, 4), #(2, 5), #(3, 6)])) + list.strict_zip([1, 2, 3], [4, 5, 6]) + |> should.equal(Ok([#(1, 4), #(2, 5), #(3, 6)])) - list.strict_zip([5, 6], [1, 2, 3]) - |> should.equal(Error(list.LengthMismatch)) + list.strict_zip([5, 6], [1, 2, 3]) + |> should.equal(Error(list.LengthMismatch)) - list.strict_zip([5, 6, 7], [1, 2]) - |> should.equal(Error(list.LengthMismatch)) -} + list.strict_zip([5, 6, 7], [1, 2]) + |> should.equal(Error(list.LengthMismatch)) + } -pub fn unzip_test() { - list.unzip([#(1, 2), #(3, 4)]) - |> should.equal(#([1, 3], [2, 4])) + pub fn unzip_test() { + list.unzip([#(1, 2), #(3, 4)]) + |> should.equal(#([1, 3], [2, 4])) - list.unzip([]) - |> should.equal(#([], [])) -} + list.unzip([]) + |> should.equal(#([], [])) + } -pub fn intersperse_test() { - list.intersperse([1, 2, 3], 4) - |> should.equal([1, 4, 2, 4, 3]) + pub fn intersperse_test() { + list.intersperse([1, 2, 3], 4) + |> should.equal([1, 4, 2, 4, 3]) - list.intersperse([], 2) - |> should.equal([]) -} + list.intersperse([], 2) + |> should.equal([]) + } -pub fn at_test() { - list.at([1, 2, 3], 2) - |> should.equal(Ok(3)) + pub fn at_test() { + list.at([1, 2, 3], 2) + |> should.equal(Ok(3)) - list.at([1, 2, 3], 5) - |> should.equal(Error(Nil)) + list.at([1, 2, 3], 5) + |> should.equal(Error(Nil)) - list.at([], 0) - |> should.equal(Error(Nil)) + list.at([], 0) + |> should.equal(Error(Nil)) - list.at([1, 2, 3, 4, 5, 6], -1) - |> should.equal(Error(Nil)) -} + list.at([1, 2, 3, 4, 5, 6], -1) + |> should.equal(Error(Nil)) + } -pub fn unique_test() { - list.unique([1, 1, 2, 3, 4, 4, 4, 5, 6]) - |> should.equal([1, 2, 3, 4, 5, 6]) + pub fn unique_test() { + list.unique([1, 1, 2, 3, 4, 4, 4, 5, 6]) + |> should.equal([1, 2, 3, 4, 5, 6]) - list.unique([7, 1, 45, 6, 2, 47, 2, 7, 5]) - |> should.equal([7, 1, 45, 6, 2, 47, 5]) + list.unique([7, 1, 45, 6, 2, 47, 2, 7, 5]) + |> should.equal([7, 1, 45, 6, 2, 47, 5]) - list.unique([3, 4, 5]) - |> should.equal([3, 4, 5]) + list.unique([3, 4, 5]) + |> should.equal([3, 4, 5]) - list.unique([]) - |> should.equal([]) -} + list.unique([]) + |> should.equal([]) + } -pub fn sort_test() { - [4, 3, 6, 5, 4] - |> list.sort(int.compare) - |> should.equal([3, 4, 4, 5, 6]) + pub fn sort_test() { + [4, 3, 6, 5, 4] + |> list.sort(int.compare) + |> should.equal([3, 4, 4, 5, 6]) - [4, 3, 6, 5, 4, 1] - |> list.sort(int.compare) - |> should.equal([1, 3, 4, 4, 5, 6]) + [4, 3, 6, 5, 4, 1] + |> list.sort(int.compare) + |> should.equal([1, 3, 4, 4, 5, 6]) - [4.1, 3.1, 6.1, 5.1, 4.1] - |> list.sort(float.compare) - |> should.equal([3.1, 4.1, 4.1, 5.1, 6.1]) + [4.1, 3.1, 6.1, 5.1, 4.1] + |> list.sort(float.compare) + |> should.equal([3.1, 4.1, 4.1, 5.1, 6.1]) - [] - |> list.sort(int.compare) - |> should.equal([]) -} + [] + |> list.sort(int.compare) + |> should.equal([]) + } -pub fn index_map_test() { - list.index_map([3, 4, 5], fn(i, x) { #(i, x) }) - |> should.equal([#(0, 3), #(1, 4), #(2, 5)]) + pub fn index_map_test() { + list.index_map([3, 4, 5], fn(i, x) { #(i, x) }) + |> should.equal([#(0, 3), #(1, 4), #(2, 5)]) - let f = fn(i, x) { string.append(x, int.to_string(i)) } - list.index_map(["a", "b", "c"], f) - |> should.equal(["a0", "b1", "c2"]) -} + let f = fn(i, x) { string.append(x, int.to_string(i)) } + list.index_map(["a", "b", "c"], f) + |> should.equal(["a0", "b1", "c2"]) + } -pub fn range_test() { - list.range(0, 0) - |> should.equal([]) + pub fn range_test() { + list.range(0, 0) + |> should.equal([]) - list.range(1, 1) - |> should.equal([]) + list.range(1, 1) + |> should.equal([]) - list.range(-1, -1) - |> should.equal([]) + list.range(-1, -1) + |> should.equal([]) - list.range(0, 1) - |> should.equal([0]) + list.range(0, 1) + |> should.equal([0]) - list.range(0, 5) - |> should.equal([0, 1, 2, 3, 4]) + list.range(0, 5) + |> should.equal([0, 1, 2, 3, 4]) - list.range(1, -5) - |> should.equal([1, 0, -1, -2, -3, -4]) -} + list.range(1, -5) + |> should.equal([1, 0, -1, -2, -3, -4]) + } -pub fn repeat_test() { - list.repeat(1, -10) - |> should.equal([]) + pub fn repeat_test() { + list.repeat(1, -10) + |> should.equal([]) - list.repeat(1, 0) - |> should.equal([]) + list.repeat(1, 0) + |> should.equal([]) - list.repeat(2, 3) - |> should.equal([2, 2, 2]) + list.repeat(2, 3) + |> should.equal([2, 2, 2]) - list.repeat("x", 5) - |> should.equal(["x", "x", "x", "x", "x"]) -} + list.repeat("x", 5) + |> should.equal(["x", "x", "x", "x", "x"]) + } -pub fn split_test() { - [] - |> list.split(0) - |> should.equal(#([], [])) + pub fn split_test() { + [] + |> list.split(0) + |> should.equal(#([], [])) - [0, 1, 2, 3, 4] - |> list.split(0) - |> should.equal(#([], [0, 1, 2, 3, 4])) + [0, 1, 2, 3, 4] + |> list.split(0) + |> should.equal(#([], [0, 1, 2, 3, 4])) - [0, 1, 2, 3, 4] - |> list.split(-2) - |> should.equal(#([], [0, 1, 2, 3, 4])) + [0, 1, 2, 3, 4] + |> list.split(-2) + |> should.equal(#([], [0, 1, 2, 3, 4])) - [0, 1, 2, 3, 4] - |> list.split(1) - |> should.equal(#([0], [1, 2, 3, 4])) + [0, 1, 2, 3, 4] + |> list.split(1) + |> should.equal(#([0], [1, 2, 3, 4])) - [0, 1, 2, 3, 4] - |> list.split(3) - |> should.equal(#([0, 1, 2], [3, 4])) + [0, 1, 2, 3, 4] + |> list.split(3) + |> should.equal(#([0, 1, 2], [3, 4])) - [0, 1, 2, 3, 4] - |> list.split(9) - |> should.equal(#([0, 1, 2, 3, 4], [])) -} + [0, 1, 2, 3, 4] + |> list.split(9) + |> should.equal(#([0, 1, 2, 3, 4], [])) + } -pub fn split_while_test() { - [] - |> list.split_while(fn(x) { x <= 5 }) - |> should.equal(#([], [])) + pub fn split_while_test() { + [] + |> list.split_while(fn(x) { x <= 5 }) + |> should.equal(#([], [])) - [1, 2, 3, 4, 5] - |> list.split_while(fn(x) { x <= 5 }) - |> should.equal(#([1, 2, 3, 4, 5], [])) + [1, 2, 3, 4, 5] + |> list.split_while(fn(x) { x <= 5 }) + |> should.equal(#([1, 2, 3, 4, 5], [])) - [1, 2, 3, 4, 5] - |> list.split_while(fn(x) { x == 2 }) - |> should.equal(#([], [1, 2, 3, 4, 5])) + [1, 2, 3, 4, 5] + |> list.split_while(fn(x) { x == 2 }) + |> should.equal(#([], [1, 2, 3, 4, 5])) - [1, 2, 3, 4, 5] - |> list.split_while(fn(x) { x <= 3 }) - |> should.equal(#([1, 2, 3], [4, 5])) + [1, 2, 3, 4, 5] + |> list.split_while(fn(x) { x <= 3 }) + |> should.equal(#([1, 2, 3], [4, 5])) - [1, 2, 3, 4, 5] - |> list.split_while(fn(x) { x <= -3 }) - |> should.equal(#([], [1, 2, 3, 4, 5])) -} + [1, 2, 3, 4, 5] + |> list.split_while(fn(x) { x <= -3 }) + |> should.equal(#([], [1, 2, 3, 4, 5])) + } -pub fn key_find_test() { - let proplist = [#(0, "1"), #(1, "2")] + pub fn key_find_test() { + let proplist = [#(0, "1"), #(1, "2")] - proplist - |> list.key_find(0) - |> should.equal(Ok("1")) + proplist + |> list.key_find(0) + |> should.equal(Ok("1")) - proplist - |> list.key_find(1) - |> should.equal(Ok("2")) + proplist + |> list.key_find(1) + |> should.equal(Ok("2")) - proplist - |> list.key_find(2) - |> should.equal(Error(Nil)) -} + proplist + |> list.key_find(2) + |> should.equal(Error(Nil)) + } -pub fn pop_test() { - list.pop([1, 2, 3], fn(x) { x > 2 }) - |> should.equal(Ok(#(3, [1, 2]))) + pub fn pop_test() { + list.pop([1, 2, 3], fn(x) { x > 2 }) + |> should.equal(Ok(#(3, [1, 2]))) - list.pop([1, 2, 3], fn(x) { x > 4 }) - |> should.equal(Error(Nil)) + list.pop([1, 2, 3], fn(x) { x > 4 }) + |> should.equal(Error(Nil)) - list.pop([], fn(_x) { True }) - |> should.equal(Error(Nil)) -} + list.pop([], fn(_x) { True }) + |> should.equal(Error(Nil)) + } -pub fn pop_map_test() { - list.pop_map(["foo", "2", "3"], int.parse) - |> should.equal(Ok(#(2, ["foo", "3"]))) + pub fn pop_map_test() { + list.pop_map(["foo", "2", "3"], int.parse) + |> should.equal(Ok(#(2, ["foo", "3"]))) - list.pop_map(["foo", "bar"], int.parse) - |> should.equal(Error(Nil)) + list.pop_map(["foo", "bar"], int.parse) + |> should.equal(Error(Nil)) - list.pop_map([], int.parse) - |> should.equal(Error(Nil)) -} + list.pop_map([], int.parse) + |> should.equal(Error(Nil)) + } -pub fn key_pop_test() { - list.key_pop([#("a", 0), #("b", 1)], "a") - |> should.equal(Ok(#(0, [#("b", 1)]))) + pub fn key_pop_test() { + list.key_pop([#("a", 0), #("b", 1)], "a") + |> should.equal(Ok(#(0, [#("b", 1)]))) - list.key_pop([#("a", 0), #("b", 1)], "b") - |> should.equal(Ok(#(1, [#("a", 0)]))) + list.key_pop([#("a", 0), #("b", 1)], "b") + |> should.equal(Ok(#(1, [#("a", 0)]))) - list.key_pop([#("a", 0), #("b", 1)], "c") - |> should.equal(Error(Nil)) -} + list.key_pop([#("a", 0), #("b", 1)], "c") + |> should.equal(Error(Nil)) + } -pub fn key_set_test() { - [#(5, 0), #(4, 1)] - |> list.key_set(4, 100) - |> should.equal([#(5, 0), #(4, 100)]) + pub fn key_set_test() { + [#(5, 0), #(4, 1)] + |> list.key_set(4, 100) + |> should.equal([#(5, 0), #(4, 100)]) - [#(5, 0), #(4, 1)] - |> list.key_set(1, 100) - |> should.equal([#(5, 0), #(4, 1), #(1, 100)]) -} + [#(5, 0), #(4, 1)] + |> list.key_set(1, 100) + |> should.equal([#(5, 0), #(4, 1), #(1, 100)]) + } -pub fn partition_test() { - [1, 2, 3, 4, 5, 6, 7] - |> list.partition(int.is_odd) - |> should.equal(#([1, 3, 5, 7], [2, 4, 6])) -} + pub fn partition_test() { + [1, 2, 3, 4, 5, 6, 7] + |> list.partition(int.is_odd) + |> should.equal(#([1, 3, 5, 7], [2, 4, 6])) + } -pub fn permutations_test() { - [1, 2] - |> list.permutations - |> should.equal([[1, 2], [2, 1]]) - - let expected = [ - [1, 2, 3], - [1, 3, 2], - [2, 1, 3], - [2, 3, 1], - [3, 1, 2], - [3, 2, 1], - ] - - [1, 2, 3] - |> list.permutations - |> should.equal(expected) - - ["a", "b"] - |> list.permutations - |> should.equal([["a", "b"], ["b", "a"]]) -} + pub fn permutations_test() { + [1, 2] + |> list.permutations + |> should.equal([[1, 2], [2, 1]]) + + let expected = [ + [1, 2, 3], + [1, 3, 2], + [2, 1, 3], + [2, 3, 1], + [3, 1, 2], + [3, 2, 1], + ] + + [1, 2, 3] + |> list.permutations + |> should.equal(expected) + + ["a", "b"] + |> list.permutations + |> should.equal([["a", "b"], ["b", "a"]]) + } -pub fn window_test() { - [1, 2, 3] - |> list.window(by: 2) - |> should.equal([[1, 2], [2, 3]]) + pub fn window_test() { + [1, 2, 3] + |> list.window(by: 2) + |> should.equal([[1, 2], [2, 3]]) - [1, 2, 3] - |> list.window(3) - |> should.equal([[1, 2, 3]]) + [1, 2, 3] + |> list.window(3) + |> should.equal([[1, 2, 3]]) - [1, 2, 3] - |> list.window(4) - |> should.equal([]) + [1, 2, 3] + |> list.window(4) + |> should.equal([]) - [1, 2, 3, 4, 5] - |> list.window(3) - |> should.equal([[1, 2, 3], [2, 3, 4], [3, 4, 5]]) -} + [1, 2, 3, 4, 5] + |> list.window(3) + |> should.equal([[1, 2, 3], [2, 3, 4], [3, 4, 5]]) + } -pub fn window_by_2_test() { - [1, 2, 3, 4] - |> list.window_by_2 - |> should.equal([#(1, 2), #(2, 3), #(3, 4)]) + pub fn window_by_2_test() { + [1, 2, 3, 4] + |> list.window_by_2 + |> should.equal([#(1, 2), #(2, 3), #(3, 4)]) - [1] - |> list.window_by_2 - |> should.equal([]) -} + [1] + |> list.window_by_2 + |> should.equal([]) + } -pub fn drop_while_test() { - [1, 2, 3, 4] - |> list.drop_while(fn(x) { x < 3 }) - |> should.equal([3, 4]) -} + pub fn drop_while_test() { + [1, 2, 3, 4] + |> list.drop_while(fn(x) { x < 3 }) + |> should.equal([3, 4]) + } -pub fn take_while_test() { - [1, 2, 3, 2, 4] - |> list.take_while(fn(x) { x < 3 }) - |> should.equal([1, 2]) -} + pub fn take_while_test() { + [1, 2, 3, 2, 4] + |> list.take_while(fn(x) { x < 3 }) + |> should.equal([1, 2]) + } -pub fn chunk_test() { - [1, 2, 2, 3, 4, 4, 6, 7, 7] - |> list.chunk(by: fn(n) { n % 2 }) - |> should.equal([[1], [2, 2], [3], [4, 4, 6], [7, 7]]) -} + pub fn chunk_test() { + [1, 2, 2, 3, 4, 4, 6, 7, 7] + |> list.chunk(by: fn(n) { n % 2 }) + |> should.equal([[1], [2, 2], [3], [4, 4, 6], [7, 7]]) + } -pub fn sized_chunk_test() { - [1, 2, 3, 4, 5, 6] - |> list.sized_chunk(into: 2) - |> should.equal([[1, 2], [3, 4], [5, 6]]) + pub fn sized_chunk_test() { + [1, 2, 3, 4, 5, 6] + |> list.sized_chunk(into: 2) + |> should.equal([[1, 2], [3, 4], [5, 6]]) - [1, 2, 3, 4, 5, 6, 7, 8] - |> list.sized_chunk(into: 3) - |> should.equal([[1, 2, 3], [4, 5, 6], [7, 8]]) -} + [1, 2, 3, 4, 5, 6, 7, 8] + |> list.sized_chunk(into: 3) + |> should.equal([[1, 2, 3], [4, 5, 6], [7, 8]]) + } -pub fn reduce_test() { - [] - |> list.reduce(with: fn(x, y) { x + y }) - |> should.equal(Error(Nil)) + pub fn reduce_test() { + [] + |> list.reduce(with: fn(x, y) { x + y }) + |> should.equal(Error(Nil)) - [1, 2, 3, 4, 5] - |> list.reduce(with: fn(x, y) { x + y }) - |> should.equal(Ok(15)) -} + [1, 2, 3, 4, 5] + |> list.reduce(with: fn(x, y) { x + y }) + |> should.equal(Ok(15)) + } -pub fn scan_test() { - [] - |> list.scan(from: 0, with: fn(i, acc) { i + acc }) - |> should.equal([]) - - [1, 2, 3, 4] - |> list.scan(from: 0, with: fn(i, acc) { 2 * i + acc }) - |> should.equal([2, 6, 12, 20]) - - [1, 2, 3, 4] - |> list.scan( - from: "", - with: fn(i, acc) { - case int.is_even(i) { - True -> "Even" - False -> "Odd" - } - |> string.append(acc, _) - }, - ) - |> should.equal(["Odd", "OddEven", "OddEvenOdd", "OddEvenOddEven"]) -} + pub fn scan_test() { + [] + |> list.scan(from: 0, with: fn(i, acc) { i + acc }) + |> should.equal([]) + + [1, 2, 3, 4] + |> list.scan(from: 0, with: fn(i, acc) { 2 * i + acc }) + |> should.equal([2, 6, 12, 20]) + + [1, 2, 3, 4] + |> list.scan( + from: "", + with: fn(i, acc) { + case int.is_even(i) { + True -> "Even" + False -> "Odd" + } + |> string.append(acc, _) + }, + ) + |> should.equal(["Odd", "OddEven", "OddEvenOdd", "OddEvenOddEven"]) + } -pub fn last_test() { - list.last([]) - |> should.equal(Error(Nil)) + pub fn last_test() { + list.last([]) + |> should.equal(Error(Nil)) - list.last([1, 2, 3, 4, 5]) - |> should.equal(Ok(5)) -} + list.last([1, 2, 3, 4, 5]) + |> should.equal(Ok(5)) + } -pub fn combinations_test() { - list.combinations([1, 2, 3], by: 0) - |> should.equal([[]]) + pub fn combinations_test() { + list.combinations([1, 2, 3], by: 0) + |> should.equal([[]]) - list.combinations([1, 2, 3], by: 1) - |> should.equal([[1], [2], [3]]) + list.combinations([1, 2, 3], by: 1) + |> should.equal([[1], [2], [3]]) - list.combinations([1, 2, 3], by: 2) - |> should.equal([[1, 2], [1, 3], [2, 3]]) + list.combinations([1, 2, 3], by: 2) + |> should.equal([[1, 2], [1, 3], [2, 3]]) - list.combinations([1, 2, 3], by: 3) - |> should.equal([[1, 2, 3]]) + list.combinations([1, 2, 3], by: 3) + |> should.equal([[1, 2, 3]]) - list.combinations([1, 2, 3], by: 4) - |> should.equal([]) + list.combinations([1, 2, 3], by: 4) + |> should.equal([]) - list.combinations([1, 2, 3, 4], 3) - |> should.equal([[1, 2, 3], [1, 2, 4], [1, 3, 4], [2, 3, 4]]) -} + list.combinations([1, 2, 3, 4], 3) + |> should.equal([[1, 2, 3], [1, 2, 4], [1, 3, 4], [2, 3, 4]]) + } -pub fn combination_pairs_test() { - list.combination_pairs([1]) - |> should.equal([]) + pub fn combination_pairs_test() { + list.combination_pairs([1]) + |> should.equal([]) - list.combination_pairs([1, 2]) - |> should.equal([#(1, 2)]) + list.combination_pairs([1, 2]) + |> should.equal([#(1, 2)]) - list.combination_pairs([1, 2, 3]) - |> should.equal([#(1, 2), #(1, 3), #(2, 3)]) + list.combination_pairs([1, 2, 3]) + |> should.equal([#(1, 2), #(1, 3), #(2, 3)]) - list.combination_pairs([1, 2, 3, 4]) - |> should.equal([#(1, 2), #(1, 3), #(1, 4), #(2, 3), #(2, 4), #(3, 4)]) -} + list.combination_pairs([1, 2, 3, 4]) + |> should.equal([#(1, 2), #(1, 3), #(1, 4), #(2, 3), #(2, 4), #(3, 4)]) + } -pub fn interleave_test() { - list.interleave([[1, 2], [101, 102]]) - |> should.equal([1, 101, 2, 102]) + pub fn interleave_test() { + list.interleave([[1, 2], [101, 102]]) + |> should.equal([1, 101, 2, 102]) - list.interleave([[1, 2], [101, 102], [201, 202]]) - |> should.equal([1, 101, 201, 2, 102, 202]) + list.interleave([[1, 2], [101, 102], [201, 202]]) + |> should.equal([1, 101, 201, 2, 102, 202]) - // Left over elements are added at the end - list.interleave([[1, 2, 3], [101, 102]]) - |> should.equal([1, 101, 2, 102, 3]) + // Left over elements are added at the end + list.interleave([[1, 2, 3], [101, 102]]) + |> should.equal([1, 101, 2, 102, 3]) - list.interleave([[1, 2], [101, 102, 103]]) - |> should.equal([1, 101, 2, 102, 103]) -} + list.interleave([[1, 2], [101, 102, 103]]) + |> should.equal([1, 101, 2, 102, 103]) + } -pub fn transpose_test() { - list.transpose([[1, 2, 3], [101, 102, 103]]) - |> should.equal([[1, 101], [2, 102], [3, 103]]) + pub fn transpose_test() { + list.transpose([[1, 2, 3], [101, 102, 103]]) + |> should.equal([[1, 101], [2, 102], [3, 103]]) - list.transpose([[1, 2, 3], [101, 102, 103], [201, 202, 203]]) - |> should.equal([[1, 101, 201], [2, 102, 202], [3, 103, 203]]) + list.transpose([[1, 2, 3], [101, 102, 103], [201, 202, 203]]) + |> should.equal([[1, 101, 201], [2, 102, 202], [3, 103, 203]]) - // Left over elements are still returned - list.transpose([[1, 2], [101, 102, 103]]) - |> should.equal([[1, 101], [2, 102], [103]]) + // Left over elements are still returned + list.transpose([[1, 2], [101, 102, 103]]) + |> should.equal([[1, 101], [2, 102], [103]]) - list.transpose([[1, 2, 3], [101, 102], [201, 202, 203]]) - |> should.equal([[1, 101, 201], [2, 102, 202], [3, 203]]) + list.transpose([[1, 2, 3], [101, 102], [201, 202, 203]]) + |> should.equal([[1, 101, 201], [2, 102, 202], [3, 203]]) + } } diff --git a/test/gleam/map_test.gleam b/test/gleam/map_test.gleam index e393a7f..6f32c8f 100644 --- a/test/gleam/map_test.gleam +++ b/test/gleam/map_test.gleam @@ -1,169 +1,171 @@ -import gleam/string -import gleam/should -import gleam/map - -pub fn from_list_test() { - [#(4, 0), #(1, 0)] - |> map.from_list - |> map.size - |> should.equal(2) - - [#(1, 0), #(1, 1)] - |> map.from_list - |> should.equal(map.from_list([#(1, 1)])) -} +if erlang { + import gleam/string + import gleam/should + import gleam/map + + pub fn from_list_test() { + [#(4, 0), #(1, 0)] + |> map.from_list + |> map.size + |> should.equal(2) + + [#(1, 0), #(1, 1)] + |> map.from_list + |> should.equal(map.from_list([#(1, 1)])) + } -pub fn has_key_test() { - [] - |> map.from_list - |> map.has_key(1) - |> should.be_false - - [#(1, 0)] - |> map.from_list - |> map.has_key(1) - |> should.be_true - - [#(4, 0), #(1, 0)] - |> map.from_list - |> map.has_key(1) - |> should.be_true - - [#(4, 0), #(1, 0)] - |> map.from_list - |> map.has_key(0) - |> should.be_false -} + pub fn has_key_test() { + [] + |> map.from_list + |> map.has_key(1) + |> should.be_false + + [#(1, 0)] + |> map.from_list + |> map.has_key(1) + |> should.be_true + + [#(4, 0), #(1, 0)] + |> map.from_list + |> map.has_key(1) + |> should.be_true + + [#(4, 0), #(1, 0)] + |> map.from_list + |> map.has_key(0) + |> should.be_false + } -pub fn new_test() { - map.new() - |> map.size - |> should.equal(0) + pub fn new_test() { + map.new() + |> map.size + |> should.equal(0) - map.new() - |> map.to_list - |> should.equal([]) -} + map.new() + |> map.to_list + |> should.equal([]) + } -pub fn get_test() { - let proplist = [#(4, 0), #(1, 1)] - let m = map.from_list(proplist) + pub fn get_test() { + let proplist = [#(4, 0), #(1, 1)] + let m = map.from_list(proplist) - m - |> map.get(4) - |> should.equal(Ok(0)) + m + |> map.get(4) + |> should.equal(Ok(0)) - m - |> map.get(1) - |> should.equal(Ok(1)) + m + |> map.get(1) + |> should.equal(Ok(1)) - m - |> map.get(2) - |> should.equal(Error(Nil)) -} + m + |> map.get(2) + |> should.equal(Error(Nil)) + } -pub fn insert_test() { - map.new() - |> map.insert("a", 0) - |> map.insert("b", 1) - |> map.insert("c", 2) - |> should.equal(map.from_list([#("a", 0), #("b", 1), #("c", 2)])) -} + pub fn insert_test() { + map.new() + |> map.insert("a", 0) + |> map.insert("b", 1) + |> map.insert("c", 2) + |> should.equal(map.from_list([#("a", 0), #("b", 1), #("c", 2)])) + } -pub fn map_values_test() { - [#(1, 0), #(2, 1), #(3, 2)] - |> map.from_list - |> map.map_values(fn(k, v) { k + v }) - |> should.equal(map.from_list([#(1, 1), #(2, 3), #(3, 5)])) -} + pub fn map_values_test() { + [#(1, 0), #(2, 1), #(3, 2)] + |> map.from_list + |> map.map_values(fn(k, v) { k + v }) + |> should.equal(map.from_list([#(1, 1), #(2, 3), #(3, 5)])) + } -pub fn keys_test() { - [#("a", 0), #("b", 1), #("c", 2)] - |> map.from_list - |> map.keys - |> should.equal(["a", "b", "c"]) -} + pub fn keys_test() { + [#("a", 0), #("b", 1), #("c", 2)] + |> map.from_list + |> map.keys + |> should.equal(["a", "b", "c"]) + } -pub fn values_test() { - [#("a", 0), #("b", 1), #("c", 2)] - |> map.from_list - |> map.values - |> should.equal([0, 1, 2]) -} + pub fn values_test() { + [#("a", 0), #("b", 1), #("c", 2)] + |> map.from_list + |> map.values + |> should.equal([0, 1, 2]) + } -pub fn take_test() { - [#("a", 0), #("b", 1), #("c", 2)] - |> map.from_list - |> map.take(["a", "b", "d"]) - |> should.equal(map.from_list([#("a", 0), #("b", 1)])) -} + pub fn take_test() { + [#("a", 0), #("b", 1), #("c", 2)] + |> map.from_list + |> map.take(["a", "b", "d"]) + |> should.equal(map.from_list([#("a", 0), #("b", 1)])) + } -pub fn drop_test() { - [#("a", 0), #("b", 1), #("c", 2)] - |> map.from_list - |> map.drop(["a", "b", "d"]) - |> should.equal(map.from_list([#("c", 2)])) -} + pub fn drop_test() { + [#("a", 0), #("b", 1), #("c", 2)] + |> map.from_list + |> map.drop(["a", "b", "d"]) + |> should.equal(map.from_list([#("c", 2)])) + } -pub fn merge_test() { - let a = map.from_list([#("a", 2), #("c", 4), #("d", 3)]) + pub fn merge_test() { + let a = map.from_list([#("a", 2), #("c", 4), #("d", 3)]) - let b = map.from_list([#("a", 0), #("b", 1), #("c", 2)]) + let b = map.from_list([#("a", 0), #("b", 1), #("c", 2)]) - map.merge(a, b) - |> should.equal(map.from_list([#("a", 0), #("b", 1), #("c", 2), #("d", 3)])) + map.merge(a, b) + |> should.equal(map.from_list([#("a", 0), #("b", 1), #("c", 2), #("d", 3)])) - map.merge(b, a) - |> should.equal(map.from_list([#("a", 2), #("b", 1), #("c", 4), #("d", 3)])) -} + map.merge(b, a) + |> should.equal(map.from_list([#("a", 2), #("b", 1), #("c", 4), #("d", 3)])) + } -pub fn delete_test() { - [#("a", 0), #("b", 1), #("c", 2)] - |> map.from_list - |> map.delete("a") - |> map.delete("d") - |> should.equal(map.from_list([#("b", 1), #("c", 2)])) -} + pub fn delete_test() { + [#("a", 0), #("b", 1), #("c", 2)] + |> map.from_list + |> map.delete("a") + |> map.delete("d") + |> should.equal(map.from_list([#("b", 1), #("c", 2)])) + } -pub fn update_test() { - let dict = map.from_list([#("a", 0), #("b", 1), #("c", 2)]) + pub fn update_test() { + let dict = map.from_list([#("a", 0), #("b", 1), #("c", 2)]) - let inc_or_zero = fn(x) { - case x { - Ok(i) -> i + 1 - Error(_) -> 0 + let inc_or_zero = fn(x) { + case x { + Ok(i) -> i + 1 + Error(_) -> 0 + } } - } - dict - |> map.update("a", inc_or_zero) - |> should.equal(map.from_list([#("a", 1), #("b", 1), #("c", 2)])) + dict + |> map.update("a", inc_or_zero) + |> should.equal(map.from_list([#("a", 1), #("b", 1), #("c", 2)])) - dict - |> map.update("b", inc_or_zero) - |> should.equal(map.from_list([#("a", 0), #("b", 2), #("c", 2)])) + dict + |> map.update("b", inc_or_zero) + |> should.equal(map.from_list([#("a", 0), #("b", 2), #("c", 2)])) - dict - |> map.update("z", inc_or_zero) - |> should.equal(map.from_list([#("a", 0), #("b", 1), #("c", 2), #("z", 0)])) -} + dict + |> map.update("z", inc_or_zero) + |> should.equal(map.from_list([#("a", 0), #("b", 1), #("c", 2), #("z", 0)])) + } -pub fn fold_test() { - let dict = map.from_list([#("a", 0), #("b", 1), #("c", 2), #("d", 3)]) + pub fn fold_test() { + let dict = map.from_list([#("a", 0), #("b", 1), #("c", 2), #("d", 3)]) - let add = fn(_, v, acc) { v + acc } + let add = fn(_, v, acc) { v + acc } - dict - |> map.fold(0, add) - |> should.equal(6) + dict + |> map.fold(0, add) + |> should.equal(6) - let concat = fn(k, _, acc) { string.append(acc, k) } + let concat = fn(k, _, acc) { string.append(acc, k) } - dict - |> map.fold("", concat) - |> should.equal("abcd") + dict + |> map.fold("", concat) + |> should.equal("abcd") - map.from_list([]) - |> map.fold(0, add) - |> should.equal(0) + map.from_list([]) + |> map.fold(0, add) + |> should.equal(0) + } } diff --git a/test/gleam/option_test.gleam b/test/gleam/option_test.gleam index 35c9eb3..32b1df6 100644 --- a/test/gleam/option_test.gleam +++ b/test/gleam/option_test.gleam @@ -1,115 +1,117 @@ -import gleam/should -import gleam/option.{None, Some} - -pub fn all_test() { - option.all([Some(1), Some(2), Some(3)]) - |> should.equal(Some([1, 2, 3])) - - option.all([Some(1), None, Some(3)]) - |> should.equal(None) -} - -pub fn is_some_test() { - option.is_some(Some(1)) - |> should.be_true - - option.is_some(None) - |> should.be_false -} - -pub fn is_none_test() { - option.is_none(Some(1)) - |> should.be_false - - option.is_none(None) - |> should.be_true -} - -pub fn to_result_test() { - option.to_result(Some(1), "possible_error") - |> should.equal(Ok(1)) - - option.to_result(None, "possible_error") - |> should.equal(Error("possible_error")) -} - -pub fn from_result_test() { - option.from_result(Ok(1)) - |> should.equal(Some(1)) - - option.from_result(Error("some_error")) - |> should.equal(None) -} - -pub fn unwrap_option_test() { - option.unwrap(Some(1), 0) - |> should.equal(1) - - option.unwrap(None, 0) - |> should.equal(0) -} - -pub fn map_option_test() { - Some(1) - |> option.map(fn(x) { x + 1 }) - |> should.equal(Some(2)) - - Some(1) - |> option.map(fn(_) { "2" }) - |> should.equal(Some("2")) - - None - |> option.map(fn(x) { x + 1 }) - |> should.equal(None) -} - -pub fn flatten_option_test() { - Some(Some(1)) - |> option.flatten() - |> should.equal(Some(1)) - - Some(None) - |> option.flatten() - |> should.equal(None) - - None - |> option.flatten() - |> should.equal(None) -} - -pub fn then_option_test() { - Some(1) - |> option.then(fn(x) { Some(x + 1) }) - |> should.equal(Some(2)) - - Some(1) - |> option.then(fn(_) { Some("2") }) - |> should.equal(Some("2")) - - None - |> option.then(fn(x) { Some(x + 1) }) - |> should.equal(None) -} - -pub fn or_option_test() { - Some(1) - |> option.or(Some(2)) - |> should.equal(Some(1)) - - Some(1) - |> option.or(None) - |> should.equal(Some(1)) - - None - |> option.or(Some(2)) - |> should.equal(Some(2)) - - None - |> option.or(None) - |> should.equal(None) -} - -pub fn values_test() { - option.values([Some(1), None, Some(3)]) - |> should.equal([1, 3]) +if erlang { + import gleam/should + import gleam/option.{None, Some} + + pub fn all_test() { + option.all([Some(1), Some(2), Some(3)]) + |> should.equal(Some([1, 2, 3])) + + option.all([Some(1), None, Some(3)]) + |> should.equal(None) + } + + pub fn is_some_test() { + option.is_some(Some(1)) + |> should.be_true + + option.is_some(None) + |> should.be_false + } + + pub fn is_none_test() { + option.is_none(Some(1)) + |> should.be_false + + option.is_none(None) + |> should.be_true + } + + pub fn to_result_test() { + option.to_result(Some(1), "possible_error") + |> should.equal(Ok(1)) + + option.to_result(None, "possible_error") + |> should.equal(Error("possible_error")) + } + + pub fn from_result_test() { + option.from_result(Ok(1)) + |> should.equal(Some(1)) + + option.from_result(Error("some_error")) + |> should.equal(None) + } + + pub fn unwrap_option_test() { + option.unwrap(Some(1), 0) + |> should.equal(1) + + option.unwrap(None, 0) + |> should.equal(0) + } + + pub fn map_option_test() { + Some(1) + |> option.map(fn(x) { x + 1 }) + |> should.equal(Some(2)) + + Some(1) + |> option.map(fn(_) { "2" }) + |> should.equal(Some("2")) + + None + |> option.map(fn(x) { x + 1 }) + |> should.equal(None) + } + + pub fn flatten_option_test() { + Some(Some(1)) + |> option.flatten() + |> should.equal(Some(1)) + + Some(None) + |> option.flatten() + |> should.equal(None) + + None + |> option.flatten() + |> should.equal(None) + } + + pub fn then_option_test() { + Some(1) + |> option.then(fn(x) { Some(x + 1) }) + |> should.equal(Some(2)) + + Some(1) + |> option.then(fn(_) { Some("2") }) + |> should.equal(Some("2")) + + None + |> option.then(fn(x) { Some(x + 1) }) + |> should.equal(None) + } + + pub fn or_option_test() { + Some(1) + |> option.or(Some(2)) + |> should.equal(Some(1)) + + Some(1) + |> option.or(None) + |> should.equal(Some(1)) + + None + |> option.or(Some(2)) + |> should.equal(Some(2)) + + None + |> option.or(None) + |> should.equal(None) + } + + pub fn values_test() { + option.values([Some(1), None, Some(3)]) + |> should.equal([1, 3]) + } } diff --git a/test/gleam/order_test.gleam b/test/gleam/order_test.gleam index 2517f9c..95cd6e8 100644 --- a/test/gleam/order_test.gleam +++ b/test/gleam/order_test.gleam @@ -1,111 +1,113 @@ -import gleam/should -import gleam/order.{Eq, Gt, Lt} +if erlang { + import gleam/should + import gleam/order.{Eq, Gt, Lt} -pub fn reverse_test() { - order.reverse(Lt) - |> should.equal(Gt) + pub fn reverse_test() { + order.reverse(Lt) + |> should.equal(Gt) - order.reverse(Eq) - |> should.equal(Eq) + order.reverse(Eq) + |> should.equal(Eq) - order.reverse(Gt) - |> should.equal(Lt) -} + order.reverse(Gt) + |> should.equal(Lt) + } -pub fn to_int_test() { - order.to_int(Lt) - |> should.equal(-1) + pub fn to_int_test() { + order.to_int(Lt) + |> should.equal(-1) - order.to_int(Eq) - |> should.equal(0) + order.to_int(Eq) + |> should.equal(0) - order.to_int(Gt) - |> should.equal(1) -} + order.to_int(Gt) + |> should.equal(1) + } -pub fn compare_test() { - order.compare(Lt, Lt) - |> should.equal(Eq) + pub fn compare_test() { + order.compare(Lt, Lt) + |> should.equal(Eq) - order.compare(Lt, Eq) - |> should.equal(Lt) + order.compare(Lt, Eq) + |> should.equal(Lt) - order.compare(Lt, Gt) - |> should.equal(Lt) + order.compare(Lt, Gt) + |> should.equal(Lt) - order.compare(Eq, Lt) - |> should.equal(Gt) + order.compare(Eq, Lt) + |> should.equal(Gt) - order.compare(Eq, Eq) - |> should.equal(Eq) + order.compare(Eq, Eq) + |> should.equal(Eq) - order.compare(Eq, Gt) - |> should.equal(Lt) + order.compare(Eq, Gt) + |> should.equal(Lt) - order.compare(Gt, Lt) - |> should.equal(Gt) + order.compare(Gt, Lt) + |> should.equal(Gt) - order.compare(Gt, Eq) - |> should.equal(Gt) + order.compare(Gt, Eq) + |> should.equal(Gt) - order.compare(Gt, Gt) - |> should.equal(Eq) -} + order.compare(Gt, Gt) + |> should.equal(Eq) + } -pub fn max_test() { - order.max(Lt, Lt) - |> should.equal(Lt) + pub fn max_test() { + order.max(Lt, Lt) + |> should.equal(Lt) - order.max(Lt, Eq) - |> should.equal(Eq) + order.max(Lt, Eq) + |> should.equal(Eq) - order.max(Lt, Gt) - |> should.equal(Gt) + order.max(Lt, Gt) + |> should.equal(Gt) - order.max(Eq, Lt) - |> should.equal(Eq) + order.max(Eq, Lt) + |> should.equal(Eq) - order.max(Eq, Eq) - |> should.equal(Eq) + order.max(Eq, Eq) + |> should.equal(Eq) - order.max(Eq, Gt) - |> should.equal(Gt) + order.max(Eq, Gt) + |> should.equal(Gt) - order.max(Gt, Lt) - |> should.equal(Gt) + order.max(Gt, Lt) + |> should.equal(Gt) - order.max(Gt, Eq) - |> should.equal(Gt) + order.max(Gt, Eq) + |> should.equal(Gt) - order.max(Gt, Gt) - |> should.equal(Gt) -} + order.max(Gt, Gt) + |> should.equal(Gt) + } -pub fn min_test() { - order.min(Lt, Lt) - |> should.equal(Lt) + pub fn min_test() { + order.min(Lt, Lt) + |> should.equal(Lt) - order.min(Lt, Eq) - |> should.equal(Lt) + order.min(Lt, Eq) + |> should.equal(Lt) - order.min(Lt, Gt) - |> should.equal(Lt) + order.min(Lt, Gt) + |> should.equal(Lt) - order.min(Eq, Lt) - |> should.equal(Lt) + order.min(Eq, Lt) + |> should.equal(Lt) - order.min(Eq, Eq) - |> should.equal(Eq) + order.min(Eq, Eq) + |> should.equal(Eq) - order.min(Eq, Gt) - |> should.equal(Eq) + order.min(Eq, Gt) + |> should.equal(Eq) - order.min(Gt, Lt) - |> should.equal(Lt) + order.min(Gt, Lt) + |> should.equal(Lt) - order.min(Gt, Eq) - |> should.equal(Eq) + order.min(Gt, Eq) + |> should.equal(Eq) - order.min(Gt, Gt) - |> should.equal(Gt) + order.min(Gt, Gt) + |> should.equal(Gt) + } } diff --git a/test/gleam/os_test.gleam b/test/gleam/os_test.gleam index 3442a90..b3d799d 100644 --- a/test/gleam/os_test.gleam +++ b/test/gleam/os_test.gleam @@ -1,47 +1,49 @@ -import gleam/map -import gleam/os -import gleam/io -import gleam/should +if erlang { + import gleam/map + import gleam/os + import gleam/io + import gleam/should -pub fn env_test() { - os.insert_env("GLEAM_TEST", "hello") - os.get_env() - |> map.get("GLEAM_TEST") - |> should.equal(Ok("hello")) + pub fn env_test() { + os.insert_env("GLEAM_TEST", "hello") + os.get_env() + |> map.get("GLEAM_TEST") + |> should.equal(Ok("hello")) - os.delete_env("GLEAM_TEST") - os.get_env() - |> map.get("GLEAM_TEST") - |> should.equal(Error(Nil)) -} + os.delete_env("GLEAM_TEST") + os.get_env() + |> map.get("GLEAM_TEST") + |> should.equal(Error(Nil)) + } -pub fn unicode_test() { - os.insert_env("GLEAM_UNICODE_TEST", "Iñtërnâtiônà£ißætiøn☃💩") - os.get_env() - |> map.get("GLEAM_UNICODE_TEST") - |> should.equal(Ok("Iñtërnâtiônà£ißætiøn☃💩")) -} + pub fn unicode_test() { + os.insert_env("GLEAM_UNICODE_TEST", "Iñtërnâtiônà£ißætiøn☃💩") + os.get_env() + |> map.get("GLEAM_UNICODE_TEST") + |> should.equal(Ok("Iñtërnâtiônà£ißætiøn☃💩")) + } -pub fn system_time_test() { - let june_12_2020 = 1591966971 - { os.system_time(os.Second) > june_12_2020 } - |> should.equal(True) - { os.system_time(os.Second) < june_12_2020 * 1000 } - |> should.equal(True) - { os.system_time(os.Millisecond) > june_12_2020 * 1000 } - |> should.equal(True) - { os.system_time(os.Millisecond) < june_12_2020 * 1000000 } - |> should.equal(True) -} + pub fn system_time_test() { + let june_12_2020 = 1591966971 + { os.system_time(os.Second) > june_12_2020 } + |> should.equal(True) + { os.system_time(os.Second) < june_12_2020 * 1000 } + |> should.equal(True) + { os.system_time(os.Millisecond) > june_12_2020 * 1000 } + |> should.equal(True) + { os.system_time(os.Millisecond) < june_12_2020 * 1000000 } + |> should.equal(True) + } -pub fn erlang_timestamp_test() { - // in microseconds - let june_12_2020 = 1591966971000000 - let #(mega_seconds, seconds, micro_seconds) = os.erlang_timestamp() + pub fn erlang_timestamp_test() { + // in microseconds + let june_12_2020 = 1591966971000000 + let #(mega_seconds, seconds, micro_seconds) = os.erlang_timestamp() - let stamp_as_micro = - { mega_seconds * 1_000_000 + seconds } * 1_000_000 + micro_seconds + let stamp_as_micro = + { mega_seconds * 1_000_000 + seconds } * 1_000_000 + micro_seconds - { stamp_as_micro > june_12_2020 } - |> should.be_true() + { stamp_as_micro > june_12_2020 } + |> should.be_true() + } } diff --git a/test/gleam/pair_test.gleam b/test/gleam/pair_test.gleam index 1950929..891ce4a 100644 --- a/test/gleam/pair_test.gleam +++ b/test/gleam/pair_test.gleam @@ -1,58 +1,60 @@ -import gleam/should -import gleam/pair - -pub fn first_test() { - #(1, 2) - |> pair.first - |> should.equal(1) - - #("abc", []) - |> pair.first - |> should.equal("abc") -} - -pub fn second_test() { - #(1, 2) - |> pair.second - |> should.equal(2) - - #("abc", []) - |> pair.second - |> should.equal([]) -} - -pub fn swap_test() { - #(1, "2") - |> pair.swap - |> should.equal(#("2", 1)) -} - -pub fn map_first_test() { - let inc = fn(a) { a + 1 } - pair.map_first(#(1, 2), inc) - |> should.equal(#(2, 2)) - - pair.map_first(#(8, 2), inc) - |> should.equal(#(9, 2)) - - pair.map_first(#(0, -2), inc) - |> should.equal(#(1, -2)) - - pair.map_first(#(-10, 20), inc) - |> should.equal(#(-9, 20)) -} - -pub fn map_second_test() { - let dec = fn(a) { a - 1 } - pair.map_second(#(1, 2), dec) - |> should.equal(#(1, 1)) - - pair.map_second(#(8, 2), dec) - |> should.equal(#(8, 1)) - - pair.map_second(#(0, -2), dec) - |> should.equal(#(0, -3)) - - pair.map_second(#(-10, 20), dec) - |> should.equal(#(-10, 19)) +if erlang { + import gleam/should + import gleam/pair + + pub fn first_test() { + #(1, 2) + |> pair.first + |> should.equal(1) + + #("abc", []) + |> pair.first + |> should.equal("abc") + } + + pub fn second_test() { + #(1, 2) + |> pair.second + |> should.equal(2) + + #("abc", []) + |> pair.second + |> should.equal([]) + } + + pub fn swap_test() { + #(1, "2") + |> pair.swap + |> should.equal(#("2", 1)) + } + + pub fn map_first_test() { + let inc = fn(a) { a + 1 } + pair.map_first(#(1, 2), inc) + |> should.equal(#(2, 2)) + + pair.map_first(#(8, 2), inc) + |> should.equal(#(9, 2)) + + pair.map_first(#(0, -2), inc) + |> should.equal(#(1, -2)) + + pair.map_first(#(-10, 20), inc) + |> should.equal(#(-9, 20)) + } + + pub fn map_second_test() { + let dec = fn(a) { a - 1 } + pair.map_second(#(1, 2), dec) + |> should.equal(#(1, 1)) + + pair.map_second(#(8, 2), dec) + |> should.equal(#(8, 1)) + + pair.map_second(#(0, -2), dec) + |> should.equal(#(0, -3)) + + pair.map_second(#(-10, 20), dec) + |> should.equal(#(-10, 19)) + } } diff --git a/test/gleam/queue_test.gleam b/test/gleam/queue_test.gleam index ae5dfeb..24c70b1 100644 --- a/test/gleam/queue_test.gleam +++ b/test/gleam/queue_test.gleam @@ -1,332 +1,334 @@ -import gleam/queue -import gleam/int -import gleam/list -import gleam/should -import gleam/pair - -pub fn from_and_to_list_test() { - queue.from_list([]) - |> should.equal(queue.new()) - - [1, 2, 3] - |> queue.from_list - |> queue.to_list - |> should.equal([1, 2, 3]) -} - -pub fn is_empty_test() { - queue.new() - |> queue.is_empty - |> should.be_true +if erlang { + import gleam/queue + import gleam/int + import gleam/list + import gleam/should + import gleam/pair - queue.from_list([""]) - |> queue.is_empty - |> should.be_false -} + pub fn from_and_to_list_test() { + queue.from_list([]) + |> should.equal(queue.new()) -pub fn length_test() { - let test = fn(input) { - queue.from_list(input) - |> queue.length - |> should.equal(list.length(input)) + [1, 2, 3] + |> queue.from_list + |> queue.to_list + |> should.equal([1, 2, 3]) } - test([]) - test([1]) - test([1, 2]) - test([1, 2, 1]) - test([1, 2, 1, 5, 2, 7, 2, 7, 8, 4, 545]) -} - -pub fn push_back_test() { - [1, 2] - |> queue.from_list - |> queue.push_back(3) - |> queue.to_list - |> should.equal([1, 2, 3]) - - queue.new() - |> queue.push_back(1) - |> queue.push_back(2) - |> queue.push_back(3) - |> queue.to_list - |> should.equal([1, 2, 3]) -} + pub fn is_empty_test() { + queue.new() + |> queue.is_empty + |> should.be_true -pub fn push_front_test() { - [2, 3] - |> queue.from_list - |> queue.push_front(1) - |> queue.push_front(0) - |> queue.to_list - |> should.equal([0, 1, 2, 3]) -} + queue.from_list([""]) + |> queue.is_empty + |> should.be_false + } -pub fn push_test() { - queue.new() - |> queue.push_front("b") - |> queue.push_back("x") - |> queue.push_front("a") - |> queue.push_back("y") - |> queue.to_list - |> should.equal(["a", "b", "x", "y"]) -} + pub fn length_test() { + let test = fn(input) { + queue.from_list(input) + |> queue.length + |> should.equal(list.length(input)) + } + + test([]) + test([1]) + test([1, 2]) + test([1, 2, 1]) + test([1, 2, 1, 5, 2, 7, 2, 7, 8, 4, 545]) + } -pub fn pop_back_test() { - assert Ok(tup) = - [1, 2, 3] + pub fn push_back_test() { + [1, 2] |> queue.from_list - |> queue.pop_back - - tup - |> pair.first - |> should.equal(3) - - tup - |> pair.second - |> queue.is_equal(queue.from_list([1, 2])) - |> should.be_true -} + |> queue.push_back(3) + |> queue.to_list + |> should.equal([1, 2, 3]) -pub fn pop_back_after_push_back_test() { - assert Ok(tup) = queue.new() |> queue.push_back(1) |> queue.push_back(2) |> queue.push_back(3) - |> queue.pop_back + |> queue.to_list + |> should.equal([1, 2, 3]) + } - tup - |> pair.first - |> should.equal(3) -} + pub fn push_front_test() { + [2, 3] + |> queue.from_list + |> queue.push_front(1) + |> queue.push_front(0) + |> queue.to_list + |> should.equal([0, 1, 2, 3]) + } -pub fn pop_back_after_push_test() { - assert Ok(tup) = + pub fn push_test() { queue.new() |> queue.push_front("b") |> queue.push_back("x") |> queue.push_front("a") |> queue.push_back("y") - |> queue.pop_back + |> queue.to_list + |> should.equal(["a", "b", "x", "y"]) + } - tup - |> pair.first - |> should.equal("y") -} + pub fn pop_back_test() { + assert Ok(tup) = + [1, 2, 3] + |> queue.from_list + |> queue.pop_back -pub fn pop_back_empty_test() { - queue.from_list([]) - |> queue.pop_back - |> should.equal(Error(Nil)) -} + tup + |> pair.first + |> should.equal(3) -pub fn pop_front_test() { - assert Ok(tup) = - [1, 2, 3] - |> queue.from_list - |> queue.pop_front + tup + |> pair.second + |> queue.is_equal(queue.from_list([1, 2])) + |> should.be_true + } - tup - |> pair.first - |> should.equal(1) + pub fn pop_back_after_push_back_test() { + assert Ok(tup) = + queue.new() + |> queue.push_back(1) + |> queue.push_back(2) + |> queue.push_back(3) + |> queue.pop_back + + tup + |> pair.first + |> should.equal(3) + } - tup - |> pair.second - |> queue.is_equal(queue.from_list([2, 3])) - |> should.be_true -} + pub fn pop_back_after_push_test() { + assert Ok(tup) = + queue.new() + |> queue.push_front("b") + |> queue.push_back("x") + |> queue.push_front("a") + |> queue.push_back("y") + |> queue.pop_back + + tup + |> pair.first + |> should.equal("y") + } -pub fn pop_front_after_push_front_test() { - assert Ok(tup) = - queue.new() - |> queue.push_front(3) - |> queue.push_front(2) - |> queue.push_front(1) - |> queue.pop_front + pub fn pop_back_empty_test() { + queue.from_list([]) + |> queue.pop_back + |> should.equal(Error(Nil)) + } - tup - |> pair.first - |> should.equal(1) -} + pub fn pop_front_test() { + assert Ok(tup) = + [1, 2, 3] + |> queue.from_list + |> queue.pop_front -pub fn pop_front_after_push_test() { - assert Ok(tup) = - queue.new() - |> queue.push_front("b") - |> queue.push_back("x") - |> queue.push_front("a") - |> queue.pop_front + tup + |> pair.first + |> should.equal(1) - tup - |> pair.first - |> should.equal("a") -} - -pub fn pop_front_empty_test() { - queue.from_list([]) - |> queue.pop_front - |> should.equal(Error(Nil)) -} + tup + |> pair.second + |> queue.is_equal(queue.from_list([2, 3])) + |> should.be_true + } -pub fn reverse_test() { - queue.from_list([1, 2, 3]) - |> queue.reverse - |> queue.to_list - |> should.equal([3, 2, 1]) - - queue.new() - |> queue.push_back(1) - |> queue.push_back(2) - |> queue.push_back(3) - |> queue.reverse - |> queue.to_list - |> should.equal([3, 2, 1]) - - queue.new() - |> queue.push_front(1) - |> queue.push_front(2) - |> queue.push_front(3) - |> queue.reverse - |> queue.to_list - |> should.equal([1, 2, 3]) - - queue.new() - |> queue.push_front(1) - |> queue.push_front(2) - |> queue.push_back(3) - |> queue.push_back(4) - |> queue.reverse - |> queue.to_list - |> should.equal([4, 3, 1, 2]) -} + pub fn pop_front_after_push_front_test() { + assert Ok(tup) = + queue.new() + |> queue.push_front(3) + |> queue.push_front(2) + |> queue.push_front(1) + |> queue.pop_front + + tup + |> pair.first + |> should.equal(1) + } -pub fn is_equal_test() { - let should_equal = fn(a, b) { - a - |> queue.is_equal(to: b) - |> should.be_true + pub fn pop_front_after_push_test() { + assert Ok(tup) = + queue.new() + |> queue.push_front("b") + |> queue.push_back("x") + |> queue.push_front("a") + |> queue.pop_front + + tup + |> pair.first + |> should.equal("a") } - let should_not_equal = fn(a, b) { - a - |> queue.is_equal(to: b) - |> should.be_false + pub fn pop_front_empty_test() { + queue.from_list([]) + |> queue.pop_front + |> should.equal(Error(Nil)) } - should_equal(queue.new(), queue.new()) + pub fn reverse_test() { + queue.from_list([1, 2, 3]) + |> queue.reverse + |> queue.to_list + |> should.equal([3, 2, 1]) - queue.new() - |> queue.push_front(1) - |> should_equal( queue.new() - |> queue.push_back(1), - ) + |> queue.push_back(1) + |> queue.push_back(2) + |> queue.push_back(3) + |> queue.reverse + |> queue.to_list + |> should.equal([3, 2, 1]) - queue.new() - |> queue.push_front(1) - |> should_equal( queue.new() - |> queue.push_front(1), - ) + |> queue.push_front(1) + |> queue.push_front(2) + |> queue.push_front(3) + |> queue.reverse + |> queue.to_list + |> should.equal([1, 2, 3]) - queue.new() - |> queue.push_back(1) - |> queue.push_back(2) - |> should_equal( queue.new() + |> queue.push_front(1) |> queue.push_front(2) - |> queue.push_front(1), - ) + |> queue.push_back(3) + |> queue.push_back(4) + |> queue.reverse + |> queue.to_list + |> should.equal([4, 3, 1, 2]) + } + + pub fn is_equal_test() { + let should_equal = fn(a, b) { + a + |> queue.is_equal(to: b) + |> should.be_true + } + + let should_not_equal = fn(a, b) { + a + |> queue.is_equal(to: b) + |> should.be_false + } + + should_equal(queue.new(), queue.new()) - queue.new() - |> queue.push_back(1) - |> should_not_equal( queue.new() - |> queue.push_front(2) - |> queue.push_front(1), - ) + |> queue.push_front(1) + |> should_equal( + queue.new() + |> queue.push_back(1), + ) - queue.new() - |> queue.push_back(2) - |> queue.push_back(1) - |> should_not_equal( queue.new() - |> queue.push_front(2) - |> queue.push_front(1), - ) -} + |> queue.push_front(1) + |> should_equal( + queue.new() + |> queue.push_front(1), + ) -pub fn is_logically_equal_test() { - let both_even_or_odd = fn(a, b) { int.is_even(a) == int.is_even(b) } + queue.new() + |> queue.push_back(1) + |> queue.push_back(2) + |> should_equal( + queue.new() + |> queue.push_front(2) + |> queue.push_front(1), + ) - let should_equal = fn(a, b) { - a - |> queue.is_logically_equal(to: b, checking: both_even_or_odd) - |> should.be_true - } + queue.new() + |> queue.push_back(1) + |> should_not_equal( + queue.new() + |> queue.push_front(2) + |> queue.push_front(1), + ) - let should_not_equal = fn(a, b) { - a - |> queue.is_logically_equal(to: b, checking: both_even_or_odd) - |> should.be_false + queue.new() + |> queue.push_back(2) + |> queue.push_back(1) + |> should_not_equal( + queue.new() + |> queue.push_front(2) + |> queue.push_front(1), + ) } - should_equal(queue.new(), queue.new()) + pub fn is_logically_equal_test() { + let both_even_or_odd = fn(a, b) { int.is_even(a) == int.is_even(b) } + + let should_equal = fn(a, b) { + a + |> queue.is_logically_equal(to: b, checking: both_even_or_odd) + |> should.be_true + } + + let should_not_equal = fn(a, b) { + a + |> queue.is_logically_equal(to: b, checking: both_even_or_odd) + |> should.be_false + } + + should_equal(queue.new(), queue.new()) - queue.new() - |> queue.push_front(3) - |> should_equal( queue.new() - |> queue.push_back(1), - ) + |> queue.push_front(3) + |> should_equal( + queue.new() + |> queue.push_back(1), + ) - queue.new() - |> queue.push_front(4) - |> should_equal( queue.new() - |> queue.push_back(2), - ) + |> queue.push_front(4) + |> should_equal( + queue.new() + |> queue.push_back(2), + ) - queue.new() - |> queue.push_front(3) - |> should_equal( queue.new() - |> queue.push_front(1), - ) + |> queue.push_front(3) + |> should_equal( + queue.new() + |> queue.push_front(1), + ) - queue.new() - |> queue.push_back(3) - |> queue.push_back(4) - |> should_equal( queue.new() - |> queue.push_front(2) - |> queue.push_front(1), - ) + |> queue.push_back(3) + |> queue.push_back(4) + |> should_equal( + queue.new() + |> queue.push_front(2) + |> queue.push_front(1), + ) - queue.new() - |> queue.push_back(1) - |> should_not_equal( queue.new() - |> queue.push_front(2) - |> queue.push_front(1), - ) + |> queue.push_back(1) + |> should_not_equal( + queue.new() + |> queue.push_front(2) + |> queue.push_front(1), + ) - queue.new() - |> queue.push_back(2) - |> queue.push_back(1) - |> should_not_equal( queue.new() - |> queue.push_front(2) - |> queue.push_front(1), - ) + |> queue.push_back(2) + |> queue.push_back(1) + |> should_not_equal( + queue.new() + |> queue.push_front(2) + |> queue.push_front(1), + ) - queue.new() - |> queue.push_back(4) - |> queue.push_back(3) - |> should_not_equal( queue.new() - |> queue.push_front(2) - |> queue.push_front(1), - ) + |> queue.push_back(4) + |> queue.push_back(3) + |> should_not_equal( + queue.new() + |> queue.push_front(2) + |> queue.push_front(1), + ) + } } diff --git a/test/gleam/regex_test.gleam b/test/gleam/regex_test.gleam index 1b45c2d..f6410e4 100644 --- a/test/gleam/regex_test.gleam +++ b/test/gleam/regex_test.gleam @@ -1,78 +1,84 @@ -import gleam/io -import gleam/option.{None, Some} -import gleam/regex.{CompileError, Match, Options} -import gleam/should +if erlang { + import gleam/io + import gleam/option.{None, Some} + import gleam/regex.{CompileError, Match, Options} + import gleam/should -pub fn from_string_test() { - assert Ok(re) = regex.from_string("[0-9]") + pub fn from_string_test() { + assert Ok(re) = regex.from_string("[0-9]") - regex.check(re, "abc123") - |> should.equal(True) + regex.check(re, "abc123") + |> should.equal(True) - regex.check(re, "abcxyz") - |> should.equal(False) + regex.check(re, "abcxyz") + |> should.equal(False) - assert Error(from_string_err) = regex.from_string("[0-9") + assert Error(from_string_err) = regex.from_string("[0-9") - from_string_err - |> should.equal(CompileError( - error: "missing terminating ] for character class", - byte_index: 4, - )) -} + from_string_err + |> should.equal(CompileError( + error: "missing terminating ] for character class", + byte_index: 4, + )) + } -pub fn compile_test() { - let options = Options(case_insensitive: True, multi_line: False) - assert Ok(re) = regex.compile("[A-B]", options) + pub fn compile_test() { + let options = Options(case_insensitive: True, multi_line: False) + assert Ok(re) = regex.compile("[A-B]", options) - regex.check(re, "abc123") - |> should.equal(True) + regex.check(re, "abc123") + |> should.equal(True) - let options = Options(case_insensitive: False, multi_line: True) - assert Ok(re) = regex.compile("^[0-9]", options) + let options = Options(case_insensitive: False, multi_line: True) + assert Ok(re) = regex.compile("^[0-9]", options) - regex.check(re, "abc\n123") - |> should.equal(True) -} + regex.check(re, "abc\n123") + |> should.equal(True) + } -pub fn check_test() { - assert Ok(re) = regex.from_string("^f.o.?") + pub fn check_test() { + assert Ok(re) = regex.from_string("^f.o.?") - regex.check(re, "foo") - |> should.equal(True) + regex.check(re, "foo") + |> should.equal(True) - regex.check(re, "boo") - |> should.equal(False) -} + regex.check(re, "boo") + |> should.equal(False) + } -pub fn split_test() { - assert Ok(re) = regex.from_string(" *, *") + pub fn split_test() { + assert Ok(re) = regex.from_string(" *, *") - regex.split(re, "foo,32, 4, 9 ,0") - |> should.equal(["foo", "32", "4", "9", "0"]) -} + regex.split(re, "foo,32, 4, 9 ,0") + |> should.equal(["foo", "32", "4", "9", "0"]) + } -pub fn scan_test() { - assert Ok(re) = regex.from_string("Gl\\w+") + pub fn scan_test() { + assert Ok(re) = regex.from_string("Gl\\w+") - regex.scan(re, "!Gleam") - |> should.equal([Match(content: "Gleam", byte_index: 1, submatches: [])]) + regex.scan(re, "!Gleam") + |> should.equal([Match(content: "Gleam", byte_index: 1, submatches: [])]) - regex.scan(re, "हGleam") - |> should.equal([Match(content: "Gleam", byte_index: 3, submatches: [])]) + regex.scan(re, "हGleam") + |> should.equal([Match(content: "Gleam", byte_index: 3, submatches: [])]) - regex.scan(re, "𐍈Gleam") - |> should.equal([Match(content: "Gleam", byte_index: 4, submatches: [])]) + regex.scan(re, "𐍈Gleam") + |> should.equal([Match(content: "Gleam", byte_index: 4, submatches: [])]) - assert Ok(re) = regex.from_string("[oi]n a(.?) (\\w+)") + assert Ok(re) = regex.from_string("[oi]n a(.?) (\\w+)") - regex.scan(re, "I am on a boat in a lake.") - |> should.equal([ - Match(content: "on a boat", byte_index: 5, submatches: [None, Some("boat")]), - Match( - content: "in a lake", - byte_index: 15, - submatches: [None, Some("lake")], - ), - ]) + regex.scan(re, "I am on a boat in a lake.") + |> should.equal([ + Match( + content: "on a boat", + byte_index: 5, + submatches: [None, Some("boat")], + ), + Match( + content: "in a lake", + byte_index: 15, + submatches: [None, Some("lake")], + ), + ]) + } } diff --git a/test/gleam/result_test.gleam b/test/gleam/result_test.gleam index a2bd6ff..464aa2b 100644 --- a/test/gleam/result_test.gleam +++ b/test/gleam/result_test.gleam @@ -1,169 +1,171 @@ -import gleam/should -import gleam/result - -pub fn is_ok_test() { - result.is_ok(Ok(1)) - |> should.be_true - - result.is_ok(Error(1)) - |> should.be_false -} - -pub fn is_error_test() { - result.is_error(Ok(1)) - |> should.be_false - - result.is_error(Error(1)) - |> should.be_true -} - -pub fn map_test() { - Ok(1) - |> result.map(fn(x) { x + 1 }) - |> should.equal(Ok(2)) - - Ok(1) - |> result.map(fn(_) { "2" }) - |> should.equal(Ok("2")) - - Error(1) - |> result.map(fn(x) { x + 1 }) - |> should.equal(Error(1)) -} - -pub fn map_error_test() { - Ok(1) - |> result.map_error(fn(x) { x + 1 }) - |> should.equal(Ok(1)) - - Error(1) - |> result.map_error(fn(x) { #("ok", x + 1) }) - |> should.equal(Error(#("ok", 2))) -} - -pub fn flatten_test() { - Ok(Ok(1)) - |> result.flatten - |> should.equal(Ok(1)) - - Ok(Error(1)) - |> result.flatten - |> should.equal(Error(1)) - - Error(1) - |> result.flatten - |> should.equal(Error(1)) - - Error(Error(1)) - |> result.flatten - |> should.equal(Error(Error(1))) -} - -pub fn then_test() { - Error(1) - |> result.then(fn(x) { Ok(x + 1) }) - |> should.equal(Error(1)) - - Ok(1) - |> result.then(fn(x) { Ok(x + 1) }) - |> should.equal(Ok(2)) - - Ok(1) - |> result.then(fn(_) { Ok("type change") }) - |> should.equal(Ok("type change")) - - Ok(1) - |> result.then(fn(_) { Error(1) }) - |> should.equal(Error(1)) -} - -pub fn unwrap_test() { - Ok(1) - |> result.unwrap(50) - |> should.equal(1) - - Error("nope") - |> result.unwrap(50) - |> should.equal(50) -} - -pub fn lazy_unwrap_test() { - Ok(1) - |> result.lazy_unwrap(fn() { 50 }) - |> should.equal(1) - - Error("nope") - |> result.lazy_unwrap(fn() { 50 }) - |> should.equal(50) -} - -pub fn nil_error_test() { - Error("error_string") - |> result.nil_error - |> should.equal(Error(Nil)) - - Error(123) - |> result.nil_error - |> should.equal(Error(Nil)) - - Ok(1) - |> result.nil_error - |> should.equal(Ok(1)) -} - -pub fn or_test() { - Ok(1) - |> result.or(Ok(2)) - |> should.equal(Ok(1)) - - Ok(1) - |> result.or(Error("Error 2")) - |> should.equal(Ok(1)) - - Error("Error 1") - |> result.or(Ok(2)) - |> should.equal(Ok(2)) - - Error("Error 1") - |> result.or(Error("Error 2")) - |> should.equal(Error("Error 2")) -} - -pub fn lazy_or_test() { - Ok(1) - |> result.lazy_or(fn() { Ok(2) }) - |> should.equal(Ok(1)) - - Ok(1) - |> result.lazy_or(fn() { Error("Error 2") }) - |> should.equal(Ok(1)) - - Error("Error 1") - |> result.lazy_or(fn() { Ok(2) }) - |> should.equal(Ok(2)) - - Error("Error 1") - |> result.lazy_or(fn() { Error("Error 2") }) - |> should.equal(Error("Error 2")) -} - -pub fn all_test() { - [Ok(1), Ok(2), Ok(3)] - |> result.all - |> should.equal(Ok([1, 2, 3])) - - [Ok(1), Error("a"), Error("b"), Ok(3)] - |> result.all - |> should.equal(Error("a")) -} - -pub fn replace_error_test() { - Error(Nil) - |> result.replace_error("Invalid") - |> should.equal(Error("Invalid")) -} - -pub fn values_test() { - result.values([Ok(1), Error(""), Ok(3)]) - |> should.equal([1, 3]) +if erlang { + import gleam/should + import gleam/result + + pub fn is_ok_test() { + result.is_ok(Ok(1)) + |> should.be_true + + result.is_ok(Error(1)) + |> should.be_false + } + + pub fn is_error_test() { + result.is_error(Ok(1)) + |> should.be_false + + result.is_error(Error(1)) + |> should.be_true + } + + pub fn map_test() { + Ok(1) + |> result.map(fn(x) { x + 1 }) + |> should.equal(Ok(2)) + + Ok(1) + |> result.map(fn(_) { "2" }) + |> should.equal(Ok("2")) + + Error(1) + |> result.map(fn(x) { x + 1 }) + |> should.equal(Error(1)) + } + + pub fn map_error_test() { + Ok(1) + |> result.map_error(fn(x) { x + 1 }) + |> should.equal(Ok(1)) + + Error(1) + |> result.map_error(fn(x) { #("ok", x + 1) }) + |> should.equal(Error(#("ok", 2))) + } + + pub fn flatten_test() { + Ok(Ok(1)) + |> result.flatten + |> should.equal(Ok(1)) + + Ok(Error(1)) + |> result.flatten + |> should.equal(Error(1)) + + Error(1) + |> result.flatten + |> should.equal(Error(1)) + + Error(Error(1)) + |> result.flatten + |> should.equal(Error(Error(1))) + } + + pub fn then_test() { + Error(1) + |> result.then(fn(x) { Ok(x + 1) }) + |> should.equal(Error(1)) + + Ok(1) + |> result.then(fn(x) { Ok(x + 1) }) + |> should.equal(Ok(2)) + + Ok(1) + |> result.then(fn(_) { Ok("type change") }) + |> should.equal(Ok("type change")) + + Ok(1) + |> result.then(fn(_) { Error(1) }) + |> should.equal(Error(1)) + } + + pub fn unwrap_test() { + Ok(1) + |> result.unwrap(50) + |> should.equal(1) + + Error("nope") + |> result.unwrap(50) + |> should.equal(50) + } + + pub fn lazy_unwrap_test() { + Ok(1) + |> result.lazy_unwrap(fn() { 50 }) + |> should.equal(1) + + Error("nope") + |> result.lazy_unwrap(fn() { 50 }) + |> should.equal(50) + } + + pub fn nil_error_test() { + Error("error_string") + |> result.nil_error + |> should.equal(Error(Nil)) + + Error(123) + |> result.nil_error + |> should.equal(Error(Nil)) + + Ok(1) + |> result.nil_error + |> should.equal(Ok(1)) + } + + pub fn or_test() { + Ok(1) + |> result.or(Ok(2)) + |> should.equal(Ok(1)) + + Ok(1) + |> result.or(Error("Error 2")) + |> should.equal(Ok(1)) + + Error("Error 1") + |> result.or(Ok(2)) + |> should.equal(Ok(2)) + + Error("Error 1") + |> result.or(Error("Error 2")) + |> should.equal(Error("Error 2")) + } + + pub fn lazy_or_test() { + Ok(1) + |> result.lazy_or(fn() { Ok(2) }) + |> should.equal(Ok(1)) + + Ok(1) + |> result.lazy_or(fn() { Error("Error 2") }) + |> should.equal(Ok(1)) + + Error("Error 1") + |> result.lazy_or(fn() { Ok(2) }) + |> should.equal(Ok(2)) + + Error("Error 1") + |> result.lazy_or(fn() { Error("Error 2") }) + |> should.equal(Error("Error 2")) + } + + pub fn all_test() { + [Ok(1), Ok(2), Ok(3)] + |> result.all + |> should.equal(Ok([1, 2, 3])) + + [Ok(1), Error("a"), Error("b"), Ok(3)] + |> result.all + |> should.equal(Error("a")) + } + + pub fn replace_error_test() { + Error(Nil) + |> result.replace_error("Invalid") + |> should.equal(Error("Invalid")) + } + + pub fn values_test() { + result.values([Ok(1), Error(""), Ok(3)]) + |> should.equal([1, 3]) + } } diff --git a/test/gleam/set_test.gleam b/test/gleam/set_test.gleam index 871c0b6..593727c 100644 --- a/test/gleam/set_test.gleam +++ b/test/gleam/set_test.gleam @@ -1,93 +1,95 @@ -import gleam/should -import gleam/set -import gleam/list -import gleam/int +if erlang { + import gleam/should + import gleam/set + import gleam/list + import gleam/int -pub fn size_test() { - set.new() - |> set.size - |> should.equal(0) + pub fn size_test() { + set.new() + |> set.size + |> should.equal(0) - set.new() - |> set.insert(1) - |> set.insert(2) - |> set.size - |> should.equal(2) + set.new() + |> set.insert(1) + |> set.insert(2) + |> set.size + |> should.equal(2) - set.new() - |> set.insert(1) - |> set.insert(1) - |> set.insert(2) - |> set.size - |> should.equal(2) -} + set.new() + |> set.insert(1) + |> set.insert(1) + |> set.insert(2) + |> set.size + |> should.equal(2) + } -pub fn contains_test() { - set.new() - |> set.insert(1) - |> set.contains(this: 1) - |> should.be_true + pub fn contains_test() { + set.new() + |> set.insert(1) + |> set.contains(this: 1) + |> should.be_true - set.new() - |> set.contains(this: 1) - |> should.be_false -} + set.new() + |> set.contains(this: 1) + |> should.be_false + } -pub fn delete_test() { - set.new() - |> set.insert(1) - |> set.delete(1) - |> set.contains(1) - |> should.be_false -} + pub fn delete_test() { + set.new() + |> set.insert(1) + |> set.delete(1) + |> set.contains(1) + |> should.be_false + } -pub fn to_list_test() { - set.new() - |> set.insert(2) - |> set.insert(3) - |> set.insert(4) - |> set.to_list - |> list.sort(by: int.compare) - |> should.equal([2, 3, 4]) -} + pub fn to_list_test() { + set.new() + |> set.insert(2) + |> set.insert(3) + |> set.insert(4) + |> set.to_list + |> list.sort(by: int.compare) + |> should.equal([2, 3, 4]) + } -pub fn from_list_test() { - [1, 1, 2, 4, 3, 2] - |> set.from_list - |> set.to_list - |> list.sort(by: int.compare) - |> should.equal([1, 2, 3, 4]) -} + pub fn from_list_test() { + [1, 1, 2, 4, 3, 2] + |> set.from_list + |> set.to_list + |> list.sort(by: int.compare) + |> should.equal([1, 2, 3, 4]) + } -pub fn fold_test() { - [1, 3, 9] - |> set.from_list - |> set.fold(from: 0, with: fn(m, a) { m + a }) -} + pub fn fold_test() { + [1, 3, 9] + |> set.from_list + |> set.fold(from: 0, with: fn(m, a) { m + a }) + } -pub fn filter_test() { - [1, 4, 6, 3, 675, 44, 67] - |> set.from_list() - |> set.filter(for: int.is_even) - |> set.to_list - |> should.equal([4, 6, 44]) -} + pub fn filter_test() { + [1, 4, 6, 3, 675, 44, 67] + |> set.from_list() + |> set.filter(for: int.is_even) + |> set.to_list + |> should.equal([4, 6, 44]) + } -pub fn take_test() { - [1, 2, 3] - |> set.from_list - |> set.take([1, 3, 5]) - |> should.equal(set.from_list([1, 3])) -} + pub fn take_test() { + [1, 2, 3] + |> set.from_list + |> set.take([1, 3, 5]) + |> should.equal(set.from_list([1, 3])) + } -pub fn union_test() { - set.union(set.from_list([1, 2]), set.from_list([2, 3])) - |> set.to_list - |> should.equal([1, 2, 3]) -} + pub fn union_test() { + set.union(set.from_list([1, 2]), set.from_list([2, 3])) + |> set.to_list + |> should.equal([1, 2, 3]) + } -pub fn intersection_test() { - set.intersection(set.from_list([1, 2]), set.from_list([2, 3])) - |> set.to_list - |> should.equal([2]) + pub fn intersection_test() { + set.intersection(set.from_list([1, 2]), set.from_list([2, 3])) + |> set.to_list + |> should.equal([2]) + } } diff --git a/test/gleam/string_builder_test.gleam b/test/gleam/string_builder_test.gleam index fdbd048..663d3b9 100644 --- a/test/gleam/string_builder_test.gleam +++ b/test/gleam/string_builder_test.gleam @@ -1,102 +1,104 @@ -import gleam/should -import gleam/string_builder - -pub fn string_builder_test() { - let data = - string_builder.from_string("ello") - |> string_builder.append(",") - |> string_builder.append(" world!") - |> string_builder.prepend("H") - - data - |> string_builder.to_string - |> should.equal("Hello, world!") - - data - |> string_builder.byte_size - |> should.equal(13) - - let data = - string_builder.from_string("ello") - |> string_builder.append_builder(string_builder.from_string(",")) - |> string_builder.append_builder(string_builder.concat([ - string_builder.from_string(" wo"), - string_builder.from_string("rld!"), - ])) - |> string_builder.prepend_builder(string_builder.from_string("H")) - - data - |> string_builder.to_string - |> should.equal("Hello, world!") - - data - |> string_builder.byte_size - |> should.equal(13) -} - -pub fn lowercase_test() { - ["Gleam", "Gleam"] - |> string_builder.from_strings - |> string_builder.lowercase - |> string_builder.to_string - |> should.equal("gleamgleam") -} - -pub fn uppercase_test() { - ["Gleam", "Gleam"] - |> string_builder.from_strings - |> string_builder.uppercase - |> string_builder.to_string - |> should.equal("GLEAMGLEAM") -} - -pub fn split_test() { - "Gleam,Erlang,Elixir" - |> string_builder.from_string - |> string_builder.split(",") - |> should.equal([ - string_builder.from_string("Gleam"), - string_builder.from_string("Erlang"), - string_builder.from_string("Elixir"), - ]) - - ["Gleam, Erl", "ang,Elixir"] - |> string_builder.from_strings - |> string_builder.split(", ") - |> should.equal([ - string_builder.from_string("Gleam"), - string_builder.from_strings(["Erl", "ang,Elixir"]), - ]) -} - -pub fn is_equal_test() { - string_builder.from_string("12") - |> string_builder.is_equal(string_builder.from_strings(["1", "2"])) - |> should.be_true - - string_builder.from_string("12") - |> string_builder.is_equal(string_builder.from_string("12")) - |> should.be_true - - string_builder.from_string("12") - |> string_builder.is_equal(string_builder.from_string("2")) - |> should.be_false -} - -pub fn is_empty_test() { - string_builder.from_string("") - |> string_builder.is_empty - |> should.be_true - - string_builder.from_string("12") - |> string_builder.is_empty - |> should.be_false - - string_builder.from_strings([]) - |> string_builder.is_empty - |> should.be_true - - string_builder.from_strings(["", ""]) - |> string_builder.is_empty - |> should.be_true +if erlang { + import gleam/should + import gleam/string_builder + + pub fn string_builder_test() { + let data = + string_builder.from_string("ello") + |> string_builder.append(",") + |> string_builder.append(" world!") + |> string_builder.prepend("H") + + data + |> string_builder.to_string + |> should.equal("Hello, world!") + + data + |> string_builder.byte_size + |> should.equal(13) + + let data = + string_builder.from_string("ello") + |> string_builder.append_builder(string_builder.from_string(",")) + |> string_builder.append_builder(string_builder.concat([ + string_builder.from_string(" wo"), + string_builder.from_string("rld!"), + ])) + |> string_builder.prepend_builder(string_builder.from_string("H")) + + data + |> string_builder.to_string + |> should.equal("Hello, world!") + + data + |> string_builder.byte_size + |> should.equal(13) + } + + pub fn lowercase_test() { + ["Gleam", "Gleam"] + |> string_builder.from_strings + |> string_builder.lowercase + |> string_builder.to_string + |> should.equal("gleamgleam") + } + + pub fn uppercase_test() { + ["Gleam", "Gleam"] + |> string_builder.from_strings + |> string_builder.uppercase + |> string_builder.to_string + |> should.equal("GLEAMGLEAM") + } + + pub fn split_test() { + "Gleam,Erlang,Elixir" + |> string_builder.from_string + |> string_builder.split(",") + |> should.equal([ + string_builder.from_string("Gleam"), + string_builder.from_string("Erlang"), + string_builder.from_string("Elixir"), + ]) + + ["Gleam, Erl", "ang,Elixir"] + |> string_builder.from_strings + |> string_builder.split(", ") + |> should.equal([ + string_builder.from_string("Gleam"), + string_builder.from_strings(["Erl", "ang,Elixir"]), + ]) + } + + pub fn is_equal_test() { + string_builder.from_string("12") + |> string_builder.is_equal(string_builder.from_strings(["1", "2"])) + |> should.be_true + + string_builder.from_string("12") + |> string_builder.is_equal(string_builder.from_string("12")) + |> should.be_true + + string_builder.from_string("12") + |> string_builder.is_equal(string_builder.from_string("2")) + |> should.be_false + } + + pub fn is_empty_test() { + string_builder.from_string("") + |> string_builder.is_empty + |> should.be_true + + string_builder.from_string("12") + |> string_builder.is_empty + |> should.be_false + + string_builder.from_strings([]) + |> string_builder.is_empty + |> should.be_true + + string_builder.from_strings(["", ""]) + |> string_builder.is_empty + |> should.be_true + } } diff --git a/test/gleam/string_test.gleam b/test/gleam/string_test.gleam index 805f3ae..085e2f9 100644 --- a/test/gleam/string_test.gleam +++ b/test/gleam/string_test.gleam @@ -1,332 +1,334 @@ -import gleam/string -import gleam/should -import gleam/order - -pub fn length_test() { - string.length("ß↑e̊") - |> should.equal(3) - - string.length("Gleam") - |> should.equal(5) - - string.length("") - |> should.equal(0) -} - -pub fn lowercase_test() { - string.lowercase("Gleam") - |> should.equal("gleam") -} - -pub fn uppercase_test() { - string.uppercase("Gleam") - |> should.equal("GLEAM") -} - -pub fn reverse_test() { - string.reverse("Gleam") - |> should.equal("maelG") -} - -pub fn split_test() { - "Gleam,Erlang,Elixir" - |> string.split(",") - |> should.equal(["Gleam", "Erlang", "Elixir"]) - - "Gleam, Erlang,Elixir" - |> string.split(", ") - |> should.equal(["Gleam", "Erlang,Elixir"]) -} - -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() { - "Gleam,Erlang,Elixir" - |> string.replace(",", "++") - |> should.equal("Gleam++Erlang++Elixir") -} - -pub fn append_test() { - "Test" - |> string.append(" Me") - |> should.equal("Test Me") -} - -pub fn compare_test() { - string.compare("", "") - |> should.equal(order.Eq) - - string.compare("a", "") - |> should.equal(order.Gt) - - string.compare("a", "A") - |> should.equal(order.Gt) - - string.compare("A", "B") - |> should.equal(order.Lt) - - string.compare("t", "ABC") - |> should.equal(order.Gt) -} - -pub fn contains_test() { - "gleam" - |> string.contains("ea") - |> should.equal(True) - - "gleam" - |> string.contains("x") - |> should.equal(False) - - string.contains(does: "bellwether", contain: "bell") - |> should.equal(True) -} - -pub fn concat_test() { - ["Hello", ", ", "world!"] - |> string.concat - |> should.equal("Hello, world!") -} - -pub fn repeat_test() { - "hi" - |> string.repeat(times: 3) - |> should.equal("hihihi") - - "hi" - |> string.repeat(0) - |> should.equal("") - - "hi" - |> string.repeat(-1) - |> should.equal("") -} - -pub fn join_test() { - ["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_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 starts_with_test() { - "theory" - |> string.starts_with("") - |> should.equal(True) - - "theory" - |> string.starts_with("the") - |> should.equal(True) - - "theory" - |> string.starts_with("ory") - |> should.equal(False) - - "theory" - |> string.starts_with("theory2") - |> should.equal(False) -} - -pub fn ends_with_test() { - "theory" - |> string.ends_with("") - |> should.equal(True) - - "theory" - |> string.ends_with("ory") - |> should.equal(True) - - "theory" - |> string.ends_with("the") - |> 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") - - "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: -2, length: 2) - |> should.equal("am") - - "gleam" - |> string.slice(at_index: -12, length: 2) - |> should.equal("") - - "gleam" - |> string.slice(at_index: 2, length: -3) - |> should.equal("") -} - -pub fn crop_test() { - "gleam" - |> string.crop("gl") - |> should.equal("gleam") - - "gleam" - |> string.crop("le") - |> should.equal("leam") - - string.crop(from: "gleam", before: "ea") - |> should.equal("eam") - - "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") - - "gleam" - |> string.drop_left(up_to: 6) - |> should.equal("") - - "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") - - "gleam" - |> string.drop_right(up_to: 5) - |> should.equal("") - - "gleam" - |> string.drop_right(up_to: -2) - |> should.equal("gleam") -} - -pub fn pad_left_test() { - "121" - |> string.pad_left(to: 5, with: ".") - |> should.equal("..121") - - "121" - |> string.pad_left(to: 3, with: ".") - |> should.equal("121") - - "121" - |> string.pad_left(to: 2, with: ".") - |> should.equal("121") - - "121" - |> string.pad_left(to: 5, with: "XY") - |> should.equal("XYXY121") -} - -pub fn pad_right_test() { - "121" - |> string.pad_right(to: 5, with: ".") - |> should.equal("121..") - - "121" - |> string.pad_right(to: 3, with: ".") - |> should.equal("121") - - "121" - |> string.pad_right(to: 2, with: ".") - |> should.equal("121") - - "121" - |> string.pad_right(to: 5, with: "XY") - |> should.equal("121XYXY") -} - -pub fn pop_grapheme_test() { - "gleam" - |> string.pop_grapheme() - |> should.equal(Ok(#("g", "leam"))) - - "g" - |> string.pop_grapheme() - |> should.equal(Ok(#("g", ""))) - - "" - |> string.pop_grapheme() - |> should.equal(Error(Nil)) -} - -pub fn to_graphemes_test() { - "abc" - |> string.to_graphemes() - |> should.equal(["a", "b", "c"]) - - "a" - |> string.to_graphemes() - |> should.equal(["a"]) - - "" - |> string.to_graphemes() - |> should.equal([]) -} - -pub fn utf_codepoint_test() { - string.utf_codepoint(1114444) - |> should.be_error - - string.utf_codepoint(65534) - |> should.be_error - - string.utf_codepoint(55296) - |> should.be_error - - assert Ok(snake) = string.utf_codepoint(128013) - should.equal(<<snake:utf8_codepoint>>, <<"🐍":utf8>>) +if erlang { + import gleam/string + import gleam/should + import gleam/order + + pub fn length_test() { + string.length("ß↑e̊") + |> should.equal(3) + + string.length("Gleam") + |> should.equal(5) + + string.length("") + |> should.equal(0) + } + + pub fn lowercase_test() { + string.lowercase("Gleam") + |> should.equal("gleam") + } + + pub fn uppercase_test() { + string.uppercase("Gleam") + |> should.equal("GLEAM") + } + + pub fn reverse_test() { + string.reverse("Gleam") + |> should.equal("maelG") + } + + pub fn split_test() { + "Gleam,Erlang,Elixir" + |> string.split(",") + |> should.equal(["Gleam", "Erlang", "Elixir"]) + + "Gleam, Erlang,Elixir" + |> string.split(", ") + |> should.equal(["Gleam", "Erlang,Elixir"]) + } + + 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() { + "Gleam,Erlang,Elixir" + |> string.replace(",", "++") + |> should.equal("Gleam++Erlang++Elixir") + } + + pub fn append_test() { + "Test" + |> string.append(" Me") + |> should.equal("Test Me") + } + + pub fn compare_test() { + string.compare("", "") + |> should.equal(order.Eq) + + string.compare("a", "") + |> should.equal(order.Gt) + + string.compare("a", "A") + |> should.equal(order.Gt) + + string.compare("A", "B") + |> should.equal(order.Lt) + + string.compare("t", "ABC") + |> should.equal(order.Gt) + } + + pub fn contains_test() { + "gleam" + |> string.contains("ea") + |> should.equal(True) + + "gleam" + |> string.contains("x") + |> should.equal(False) + + string.contains(does: "bellwether", contain: "bell") + |> should.equal(True) + } + + pub fn concat_test() { + ["Hello", ", ", "world!"] + |> string.concat + |> should.equal("Hello, world!") + } + + pub fn repeat_test() { + "hi" + |> string.repeat(times: 3) + |> should.equal("hihihi") + + "hi" + |> string.repeat(0) + |> should.equal("") + + "hi" + |> string.repeat(-1) + |> should.equal("") + } + + pub fn join_test() { + ["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_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 starts_with_test() { + "theory" + |> string.starts_with("") + |> should.equal(True) + + "theory" + |> string.starts_with("the") + |> should.equal(True) + + "theory" + |> string.starts_with("ory") + |> should.equal(False) + + "theory" + |> string.starts_with("theory2") + |> should.equal(False) + } + + pub fn ends_with_test() { + "theory" + |> string.ends_with("") + |> should.equal(True) + + "theory" + |> string.ends_with("ory") + |> should.equal(True) + + "theory" + |> string.ends_with("the") + |> 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") + + "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: -2, length: 2) + |> should.equal("am") + + "gleam" + |> string.slice(at_index: -12, length: 2) + |> should.equal("") + + "gleam" + |> string.slice(at_index: 2, length: -3) + |> should.equal("") + } + + pub fn crop_test() { + "gleam" + |> string.crop("gl") + |> should.equal("gleam") + + "gleam" + |> string.crop("le") + |> should.equal("leam") + + string.crop(from: "gleam", before: "ea") + |> should.equal("eam") + + "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") + + "gleam" + |> string.drop_left(up_to: 6) + |> should.equal("") + + "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") + + "gleam" + |> string.drop_right(up_to: 5) + |> should.equal("") + + "gleam" + |> string.drop_right(up_to: -2) + |> should.equal("gleam") + } + + pub fn pad_left_test() { + "121" + |> string.pad_left(to: 5, with: ".") + |> should.equal("..121") + + "121" + |> string.pad_left(to: 3, with: ".") + |> should.equal("121") + + "121" + |> string.pad_left(to: 2, with: ".") + |> should.equal("121") + + "121" + |> string.pad_left(to: 5, with: "XY") + |> should.equal("XYXY121") + } + + pub fn pad_right_test() { + "121" + |> string.pad_right(to: 5, with: ".") + |> should.equal("121..") + + "121" + |> string.pad_right(to: 3, with: ".") + |> should.equal("121") + + "121" + |> string.pad_right(to: 2, with: ".") + |> should.equal("121") + + "121" + |> string.pad_right(to: 5, with: "XY") + |> should.equal("121XYXY") + } + + pub fn pop_grapheme_test() { + "gleam" + |> string.pop_grapheme() + |> should.equal(Ok(#("g", "leam"))) + + "g" + |> string.pop_grapheme() + |> should.equal(Ok(#("g", ""))) + + "" + |> string.pop_grapheme() + |> should.equal(Error(Nil)) + } + + pub fn to_graphemes_test() { + "abc" + |> string.to_graphemes() + |> should.equal(["a", "b", "c"]) + + "a" + |> string.to_graphemes() + |> should.equal(["a"]) + + "" + |> string.to_graphemes() + |> should.equal([]) + } + + pub fn utf_codepoint_test() { + string.utf_codepoint(1114444) + |> should.be_error + + string.utf_codepoint(65534) + |> should.be_error + + string.utf_codepoint(55296) + |> should.be_error + + assert Ok(snake) = string.utf_codepoint(128013) + should.equal(<<snake:utf8_codepoint>>, <<"🐍":utf8>>) + } } diff --git a/test/gleam/uri_test.gleam b/test/gleam/uri_test.gleam index c0ee595..9c6d2e0 100644 --- a/test/gleam/uri_test.gleam +++ b/test/gleam/uri_test.gleam @@ -1,294 +1,296 @@ -import gleam/uri -import gleam/should -import gleam/string -import gleam/list -import gleam/option.{None, Some} - -pub fn full_parse_test() { - assert Ok(parsed) = - uri.parse("https://foo:bar@example.com:1234/path?query=true#fragment") - should.equal(parsed.scheme, Some("https")) - should.equal(parsed.userinfo, Some("foo:bar")) - should.equal(parsed.host, Some("example.com")) - should.equal(parsed.port, Some(1234)) - should.equal(parsed.path, "/path") - should.equal(parsed.query, Some("query=true")) - should.equal(parsed.fragment, Some("fragment")) -} - -pub fn parse_only_path_test() { - assert Ok(parsed) = uri.parse("") - should.equal(parsed.scheme, None) - should.equal(parsed.userinfo, None) - should.equal(parsed.host, None) - should.equal(parsed.port, None) - should.equal(parsed.path, "") - should.equal(parsed.query, None) - should.equal(parsed.fragment, None) -} - -pub fn parse_only_host_test() { - assert Ok(parsed) = uri.parse("//") - should.equal(parsed.scheme, None) - should.equal(parsed.userinfo, None) - should.equal(parsed.host, Some("")) - should.equal(parsed.port, None) - should.equal(parsed.path, "") - should.equal(parsed.query, None) - should.equal(parsed.fragment, None) -} - -pub fn error_parsing_uri_test() { - should.equal(uri.parse("::"), Error(Nil)) -} - -pub fn full_uri_to_string_test() { - let test_uri = - uri.Uri( - Some("https"), - Some("foo:bar"), - Some("example.com"), - Some(1234), - "/path", - Some("query=true"), - Some("fragment"), +if erlang { + import gleam/uri + import gleam/should + import gleam/string + import gleam/list + import gleam/option.{None, Some} + + pub fn full_parse_test() { + assert Ok(parsed) = + uri.parse("https://foo:bar@example.com:1234/path?query=true#fragment") + should.equal(parsed.scheme, Some("https")) + should.equal(parsed.userinfo, Some("foo:bar")) + should.equal(parsed.host, Some("example.com")) + should.equal(parsed.port, Some(1234)) + should.equal(parsed.path, "/path") + should.equal(parsed.query, Some("query=true")) + should.equal(parsed.fragment, Some("fragment")) + } + + pub fn parse_only_path_test() { + assert Ok(parsed) = uri.parse("") + should.equal(parsed.scheme, None) + should.equal(parsed.userinfo, None) + should.equal(parsed.host, None) + should.equal(parsed.port, None) + should.equal(parsed.path, "") + should.equal(parsed.query, None) + should.equal(parsed.fragment, None) + } + + pub fn parse_only_host_test() { + assert Ok(parsed) = uri.parse("//") + should.equal(parsed.scheme, None) + should.equal(parsed.userinfo, None) + should.equal(parsed.host, Some("")) + should.equal(parsed.port, None) + should.equal(parsed.path, "") + should.equal(parsed.query, None) + should.equal(parsed.fragment, None) + } + + pub fn error_parsing_uri_test() { + should.equal(uri.parse("::"), Error(Nil)) + } + + pub fn full_uri_to_string_test() { + let test_uri = + uri.Uri( + Some("https"), + Some("foo:bar"), + Some("example.com"), + Some(1234), + "/path", + Some("query=true"), + Some("fragment"), + ) + should.equal( + uri.to_string(test_uri), + "https://foo:bar@example.com:1234/path?query=true#fragment", ) - should.equal( - uri.to_string(test_uri), - "https://foo:bar@example.com:1234/path?query=true#fragment", - ) -} - -pub fn path_only_uri_to_string_test() { - let test_uri = uri.Uri(None, None, None, None, "/", None, None) - should.equal(uri.to_string(test_uri), "/") -} - -pub fn parse_query_string_test() { - assert Ok(parsed) = uri.parse_query("foo+bar=1&city=%C3%B6rebro") - should.equal(parsed, [#("foo bar", "1"), #("city", "örebro")]) - - // Duplicates keys not overridden - assert Ok(parsed) = uri.parse_query("a[]=1&a[]=2") - - parsed - |> should.equal([#("a[]", "1"), #("a[]", "2")]) -} - -pub fn parse_empty_query_string_test() { - assert Ok(parsed) = uri.parse_query("") - should.equal(parsed, []) -} - -pub fn parse_query_string_with_empty_test() { - uri.parse_query("present") - |> should.equal(Ok([#("present", "")])) -} - -pub fn error_parsing_query_test() { - should.equal(uri.parse_query("%C2"), Error(Nil)) -} - -pub fn query_to_string_test() { - let query_string = - uri.query_to_string([#("foo bar", "1"), #("city", "örebro")]) - should.equal(query_string, "foo+bar=1&city=%C3%B6rebro") -} - -pub fn empty_query_to_string_test() { - let query_string = uri.query_to_string([]) - should.equal(query_string, "") -} - -fn percent_codec_fixtures() { - [ - #(" ", "+"), - #(",", "%2C"), - #(";", "%3B"), - #(":", "%3A"), - #("!", "%21"), - #("?", "%3F"), - #("'", "%27"), - #("(", "%28"), - #(")", "%29"), - #("[", "%5B"), - #("@", "%40"), - #("/", "%2F"), - #("\\", "%5C"), - #("&", "%26"), - #("#", "%23"), - #("=", "%3D"), - #("~", "%7E"), - #("ñ", "%C3%B1"), - // Allowed chars - #("-", "-"), - #("_", "_"), - #(".", "."), - #("*", "*"), - #("100% great", "100%25+great"), - ] -} - -pub fn percent_encode_test() { - percent_codec_fixtures() - |> list.map(fn(t) { - let #(a, b) = t - uri.percent_encode(a) - |> should.equal(b) - }) -} - -pub fn percent_encode_consistency_test() { - let k = "foo bar[]" - let v = "ñaña (,:*~)" - - let query_string = uri.query_to_string([#(k, v)]) - - let encoded_key = uri.percent_encode(k) - let encoded_value = uri.percent_encode(v) - let manual_query_string = string.concat([encoded_key, "=", encoded_value]) - - should.equal(query_string, manual_query_string) -} - -pub fn percent_decode_test() { - percent_codec_fixtures() - |> list.map(fn(t) { - let #(a, b) = t - uri.percent_decode(b) - |> should.equal(Ok(a)) - }) -} - -pub fn percent_decode_consistency_test() { - let k = "foo+bar[]" - let v = "%C3%B6rebro" - let query = string.concat([k, "=", v]) - assert Ok(parsed) = uri.parse_query(query) - - assert Ok(decoded_key) = uri.percent_decode(k) - assert Ok(decoded_value) = uri.percent_decode(v) - - should.equal(parsed, [#(decoded_key, decoded_value)]) -} - -pub fn parse_segments_test() { - should.equal(uri.path_segments("/"), []) - should.equal(uri.path_segments("/foo/bar"), ["foo", "bar"]) - should.equal(uri.path_segments("////"), []) - should.equal(uri.path_segments("/foo//bar"), ["foo", "bar"]) - - should.equal(uri.path_segments("/."), []) - should.equal(uri.path_segments("/.foo"), [".foo"]) - - should.equal(uri.path_segments("/../bar"), ["bar"]) - should.equal(uri.path_segments("../bar"), ["bar"]) - should.equal(uri.path_segments("/foo/../bar"), ["bar"]) -} - -pub fn origin_test() { - assert Ok(parsed) = uri.parse("http://example.test/path?foo#bar") - uri.origin(parsed) - |> should.equal(Ok("http://example.test")) - - assert Ok(parsed) = uri.parse("http://example.test:8080") - uri.origin(parsed) - |> should.equal(Ok("http://example.test:8080")) - - assert Ok(parsed) = uri.parse("https://example.test") - uri.origin(parsed) - |> should.equal(Ok("https://example.test")) - - assert Ok(parsed) = uri.parse("http:///path") - uri.origin(parsed) - |> should.equal(Ok("http://")) - - assert Ok(parsed) = uri.parse("http://") - uri.origin(parsed) - |> should.equal(Ok("http://")) - - assert Ok(parsed) = uri.parse("/path") - uri.origin(parsed) - |> should.equal(Error(Nil)) - - assert Ok(parsed) = uri.parse("file:///dev/null") - uri.origin(parsed) - |> should.equal(Error(Nil)) -} - -pub fn merge_test() { - assert Ok(a) = uri.parse("/relative") - assert Ok(b) = uri.parse("") - uri.merge(a, b) - |> should.equal(Error(Nil)) - - assert Ok(a) = uri.parse("http://google.com/foo") - assert Ok(b) = uri.parse("http://example.com/baz") - uri.merge(a, b) - |> should.equal(uri.parse("http://example.com/baz")) - - assert Ok(a) = uri.parse("http://google.com/foo") - assert Ok(b) = uri.parse("http://example.com/.././bar/../../baz") - uri.merge(a, b) - |> should.equal(uri.parse("http://example.com/baz")) - - assert Ok(a) = uri.parse("http://google.com/foo") - assert Ok(b) = uri.parse("//example.com/baz") - uri.merge(a, b) - |> should.equal(uri.parse("http://example.com/baz")) - - assert Ok(a) = uri.parse("http://google.com/foo") - assert Ok(b) = uri.parse("//example.com/.././bar/../../../baz") - uri.merge(a, b) - |> should.equal(uri.parse("http://example.com/baz")) - - assert Ok(a) = uri.parse("http://example.com/foo/bar") - assert Ok(b) = uri.parse("/baz") - uri.merge(a, b) - |> should.equal(uri.parse("http://example.com/baz")) - - assert Ok(a) = uri.parse("http://example.com/foo/bar") - assert Ok(b) = uri.parse("baz") - uri.merge(a, b) - |> should.equal(uri.parse("http://example.com/foo/baz")) - - assert Ok(a) = uri.parse("http://example.com/foo/") - assert Ok(b) = uri.parse("baz") - uri.merge(a, b) - |> should.equal(uri.parse("http://example.com/foo/baz")) - - assert Ok(a) = uri.parse("http://example.com") - assert Ok(b) = uri.parse("baz") - uri.merge(a, b) - |> should.equal(uri.parse("http://example.com/baz")) - - assert Ok(a) = uri.parse("http://example.com") - assert Ok(b) = uri.parse("/.././bar/../../../baz") - uri.merge(a, b) - |> should.equal(uri.parse("http://example.com/baz")) - - assert Ok(a) = uri.parse("http://example.com/foo/bar") - assert Ok(b) = uri.parse("") - uri.merge(a, b) - |> should.equal(uri.parse("http://example.com/foo/bar")) - - assert Ok(a) = uri.parse("http://example.com/foo/bar") - assert Ok(b) = uri.parse("#fragment") - uri.merge(a, b) - |> should.equal(uri.parse("http://example.com/foo/bar#fragment")) - - assert Ok(a) = uri.parse("http://example.com/foo/bar") - assert Ok(b) = uri.parse("?query") - uri.merge(a, b) - |> should.equal(uri.parse("http://example.com/foo/bar?query")) - - assert Ok(a) = uri.parse("http://example.com/foo/bar?query1") - assert Ok(b) = uri.parse("?query2") - uri.merge(a, b) - |> should.equal(uri.parse("http://example.com/foo/bar?query2")) - - assert Ok(a) = uri.parse("http://example.com/foo/bar?query") - assert Ok(b) = uri.parse("") - uri.merge(a, b) - |> should.equal(uri.parse("http://example.com/foo/bar?query")) + } + + pub fn path_only_uri_to_string_test() { + let test_uri = uri.Uri(None, None, None, None, "/", None, None) + should.equal(uri.to_string(test_uri), "/") + } + + pub fn parse_query_string_test() { + assert Ok(parsed) = uri.parse_query("foo+bar=1&city=%C3%B6rebro") + should.equal(parsed, [#("foo bar", "1"), #("city", "örebro")]) + + // Duplicates keys not overridden + assert Ok(parsed) = uri.parse_query("a[]=1&a[]=2") + + parsed + |> should.equal([#("a[]", "1"), #("a[]", "2")]) + } + + pub fn parse_empty_query_string_test() { + assert Ok(parsed) = uri.parse_query("") + should.equal(parsed, []) + } + + pub fn parse_query_string_with_empty_test() { + uri.parse_query("present") + |> should.equal(Ok([#("present", "")])) + } + + pub fn error_parsing_query_test() { + should.equal(uri.parse_query("%C2"), Error(Nil)) + } + + pub fn query_to_string_test() { + let query_string = + uri.query_to_string([#("foo bar", "1"), #("city", "örebro")]) + should.equal(query_string, "foo+bar=1&city=%C3%B6rebro") + } + + pub fn empty_query_to_string_test() { + let query_string = uri.query_to_string([]) + should.equal(query_string, "") + } + + fn percent_codec_fixtures() { + [ + #(" ", "+"), + #(",", "%2C"), + #(";", "%3B"), + #(":", "%3A"), + #("!", "%21"), + #("?", "%3F"), + #("'", "%27"), + #("(", "%28"), + #(")", "%29"), + #("[", "%5B"), + #("@", "%40"), + #("/", "%2F"), + #("\\", "%5C"), + #("&", "%26"), + #("#", "%23"), + #("=", "%3D"), + #("~", "%7E"), + #("ñ", "%C3%B1"), + // Allowed chars + #("-", "-"), + #("_", "_"), + #(".", "."), + #("*", "*"), + #("100% great", "100%25+great"), + ] + } + + pub fn percent_encode_test() { + percent_codec_fixtures() + |> list.map(fn(t) { + let #(a, b) = t + uri.percent_encode(a) + |> should.equal(b) + }) + } + + pub fn percent_encode_consistency_test() { + let k = "foo bar[]" + let v = "ñaña (,:*~)" + + let query_string = uri.query_to_string([#(k, v)]) + + let encoded_key = uri.percent_encode(k) + let encoded_value = uri.percent_encode(v) + let manual_query_string = string.concat([encoded_key, "=", encoded_value]) + + should.equal(query_string, manual_query_string) + } + + pub fn percent_decode_test() { + percent_codec_fixtures() + |> list.map(fn(t) { + let #(a, b) = t + uri.percent_decode(b) + |> should.equal(Ok(a)) + }) + } + + pub fn percent_decode_consistency_test() { + let k = "foo+bar[]" + let v = "%C3%B6rebro" + let query = string.concat([k, "=", v]) + assert Ok(parsed) = uri.parse_query(query) + + assert Ok(decoded_key) = uri.percent_decode(k) + assert Ok(decoded_value) = uri.percent_decode(v) + + should.equal(parsed, [#(decoded_key, decoded_value)]) + } + + pub fn parse_segments_test() { + should.equal(uri.path_segments("/"), []) + should.equal(uri.path_segments("/foo/bar"), ["foo", "bar"]) + should.equal(uri.path_segments("////"), []) + should.equal(uri.path_segments("/foo//bar"), ["foo", "bar"]) + + should.equal(uri.path_segments("/."), []) + should.equal(uri.path_segments("/.foo"), [".foo"]) + + should.equal(uri.path_segments("/../bar"), ["bar"]) + should.equal(uri.path_segments("../bar"), ["bar"]) + should.equal(uri.path_segments("/foo/../bar"), ["bar"]) + } + + pub fn origin_test() { + assert Ok(parsed) = uri.parse("http://example.test/path?foo#bar") + uri.origin(parsed) + |> should.equal(Ok("http://example.test")) + + assert Ok(parsed) = uri.parse("http://example.test:8080") + uri.origin(parsed) + |> should.equal(Ok("http://example.test:8080")) + + assert Ok(parsed) = uri.parse("https://example.test") + uri.origin(parsed) + |> should.equal(Ok("https://example.test")) + + assert Ok(parsed) = uri.parse("http:///path") + uri.origin(parsed) + |> should.equal(Ok("http://")) + + assert Ok(parsed) = uri.parse("http://") + uri.origin(parsed) + |> should.equal(Ok("http://")) + + assert Ok(parsed) = uri.parse("/path") + uri.origin(parsed) + |> should.equal(Error(Nil)) + + assert Ok(parsed) = uri.parse("file:///dev/null") + uri.origin(parsed) + |> should.equal(Error(Nil)) + } + + pub fn merge_test() { + assert Ok(a) = uri.parse("/relative") + assert Ok(b) = uri.parse("") + uri.merge(a, b) + |> should.equal(Error(Nil)) + + assert Ok(a) = uri.parse("http://google.com/foo") + assert Ok(b) = uri.parse("http://example.com/baz") + uri.merge(a, b) + |> should.equal(uri.parse("http://example.com/baz")) + + assert Ok(a) = uri.parse("http://google.com/foo") + assert Ok(b) = uri.parse("http://example.com/.././bar/../../baz") + uri.merge(a, b) + |> should.equal(uri.parse("http://example.com/baz")) + + assert Ok(a) = uri.parse("http://google.com/foo") + assert Ok(b) = uri.parse("//example.com/baz") + uri.merge(a, b) + |> should.equal(uri.parse("http://example.com/baz")) + + assert Ok(a) = uri.parse("http://google.com/foo") + assert Ok(b) = uri.parse("//example.com/.././bar/../../../baz") + uri.merge(a, b) + |> should.equal(uri.parse("http://example.com/baz")) + + assert Ok(a) = uri.parse("http://example.com/foo/bar") + assert Ok(b) = uri.parse("/baz") + uri.merge(a, b) + |> should.equal(uri.parse("http://example.com/baz")) + + assert Ok(a) = uri.parse("http://example.com/foo/bar") + assert Ok(b) = uri.parse("baz") + uri.merge(a, b) + |> should.equal(uri.parse("http://example.com/foo/baz")) + + assert Ok(a) = uri.parse("http://example.com/foo/") + assert Ok(b) = uri.parse("baz") + uri.merge(a, b) + |> should.equal(uri.parse("http://example.com/foo/baz")) + + assert Ok(a) = uri.parse("http://example.com") + assert Ok(b) = uri.parse("baz") + uri.merge(a, b) + |> should.equal(uri.parse("http://example.com/baz")) + + assert Ok(a) = uri.parse("http://example.com") + assert Ok(b) = uri.parse("/.././bar/../../../baz") + uri.merge(a, b) + |> should.equal(uri.parse("http://example.com/baz")) + + assert Ok(a) = uri.parse("http://example.com/foo/bar") + assert Ok(b) = uri.parse("") + uri.merge(a, b) + |> should.equal(uri.parse("http://example.com/foo/bar")) + + assert Ok(a) = uri.parse("http://example.com/foo/bar") + assert Ok(b) = uri.parse("#fragment") + uri.merge(a, b) + |> should.equal(uri.parse("http://example.com/foo/bar#fragment")) + + assert Ok(a) = uri.parse("http://example.com/foo/bar") + assert Ok(b) = uri.parse("?query") + uri.merge(a, b) + |> should.equal(uri.parse("http://example.com/foo/bar?query")) + + assert Ok(a) = uri.parse("http://example.com/foo/bar?query1") + assert Ok(b) = uri.parse("?query2") + uri.merge(a, b) + |> should.equal(uri.parse("http://example.com/foo/bar?query2")) + + assert Ok(a) = uri.parse("http://example.com/foo/bar?query") + assert Ok(b) = uri.parse("") + uri.merge(a, b) + |> should.equal(uri.parse("http://example.com/foo/bar?query")) + } } |