diff options
author | Louis Pilfold <louis@lpil.uk> | 2021-07-31 11:26:38 +0100 |
---|---|---|
committer | Louis Pilfold <louis@lpil.uk> | 2021-07-31 11:26:38 +0100 |
commit | f78920f014e2e2b3d512bd5703902bbdba68eb4f (patch) | |
tree | a8dbc823a3223fb67f2d8e34ef4287f4de3f161d | |
parent | 942521b3016af2f24bff4eb1237c01a7856ffea5 (diff) | |
download | gleam_stdlib-f78920f014e2e2b3d512bd5703902bbdba68eb4f.tar.gz gleam_stdlib-f78920f014e2e2b3d512bd5703902bbdba68eb4f.zip |
Move atom module to gleam_erlang library
-rw-r--r-- | CHANGELOG.md | 5 | ||||
-rw-r--r-- | src/gleam/atom.gleam | 64 | ||||
-rw-r--r-- | src/gleam/dynamic.gleam | 73 | ||||
-rw-r--r-- | src/gleam/io.gleam | 75 | ||||
-rw-r--r-- | src/gleam/string.gleam | 4 | ||||
-rw-r--r-- | src/gleam_stdlib.erl | 45 | ||||
-rw-r--r-- | src/gleam_stdlib.js | 8 | ||||
-rw-r--r-- | test/gleam/atom_test.gleam | 47 | ||||
-rw-r--r-- | test/gleam/dynamic_test.gleam | 78 |
9 files changed, 140 insertions, 259 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 1986086..10c6844 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,11 @@ - The `map.update` function now uses `Option` rather than `Result`. - The `iterator` module gains the `fold_until` function. - The `bit_string` module loses the u32 functions in favour of bit string literals. +- The `dynamic` module loses the `atom` function +- The `dynamic.option` function has been renamed to `optional` and made more + permissive to other null values. +- The `dynamic.result` function has been made more permissive to other result values. +- The `atom` module has been moved to the `gleam_erlang` library. - Prelude types like `Result`, `List` etc. are no longer redefined in their stdlib modules. - All modules have been updated to work when running on both Erlang and diff --git a/src/gleam/atom.gleam b/src/gleam/atom.gleam deleted file mode 100644 index 2d96053..0000000 --- a/src/gleam/atom.gleam +++ /dev/null @@ -1,64 +0,0 @@ -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 - } - - /// 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" - - /// 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/dynamic.gleam b/src/gleam/dynamic.gleam index 8516b40..a51fd0e 100644 --- a/src/gleam/dynamic.gleam +++ b/src/gleam/dynamic.gleam @@ -1,9 +1,8 @@ if erlang { - import gleam/atom import gleam/bit_string import gleam/list import gleam/map.{Map} - import gleam/option.{None, Option, Some} + import gleam/option.{Option} import gleam/result import gleam/string_builder @@ -93,21 +92,6 @@ if erlang { 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. /// @@ -156,7 +140,7 @@ if erlang { "gleam_stdlib" "decode_list" /// Checks to see whether a Dynamic value is a result, and return the result if - /// it is + /// it is. /// /// ## Examples /// @@ -169,24 +153,8 @@ if erlang { /// > 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 - } - } + pub external fn result(Dynamic) -> Result(Result(Dynamic, Dynamic), String) = + "gleam_stdlib" "decode_result" /// Checks to see whether a Dynamic value is a result of a particular type, and /// return the result if it is @@ -254,35 +222,34 @@ if erlang { |> 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. + /// Checks to see if a Dynamic value is a nullable version of a particular + /// type, and return the Option if it is. /// /// ## Examples /// /// > option(from("Hello"), string) /// Ok(Some("Hello")) /// + /// > option(from("Hello"), string) + /// Ok(Some("Hello")) + /// /// > option(from(atom.from_string("null")), string) /// Ok(None) /// + /// > option(from(atom.from_string("nil")), string) + /// Ok(None) + /// + /// > option(from(atom.from_string("undefined")), 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) - } - } + pub external fn optional( + from: Dynamic, + of: Decoder(inner), + ) -> Result(Option(inner), String) = + "gleam_stdlib" "decode_optional" /// Checks to see if a Dynamic value is a map with a specific field, and return /// the value of the field if it is. diff --git a/src/gleam/io.gleam b/src/gleam/io.gleam index 9482843..4b61eec 100644 --- a/src/gleam/io.gleam +++ b/src/gleam/io.gleam @@ -1,36 +1,55 @@ -if erlang { - external type DoNotLeak - - external fn erl_print(String, List(a)) -> DoNotLeak = - "io" "fwrite" +/// Writes a string to standard output. +/// +/// If you want your output to be printed on its own line see `println`. +/// +/// ## Example +/// +/// ``` +/// > io.print("Hi mum") +/// // -> Hi mum +/// Nil +/// ``` +/// +pub fn print(string: String) -> Nil { + do_print(string) +} - /// Writes a string to standard output. - /// - /// ## Example - /// - /// > io.print("Hi mum") - /// // -> Hi mum - /// Nil - /// - pub fn print(string: String) -> Nil { +if erlang { + fn do_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 { +if javascript { + external fn do_print(String) -> Nil = + "../gleam_stdlib.js" "print" +} + +/// 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 { + do_println(string) +} + +if erlang { + fn do_println(string: String) -> Nil { erl_print("~ts\n", [string]) Nil } } +if javascript { + external fn do_println(String) -> Nil = + "../gleam_stdlib.js" "log" +} + /// Prints a value to standard output using Erlang syntax. /// /// The value is returned after being printed so it can be used in pipelines. @@ -66,7 +85,7 @@ if erlang { if javascript { external fn debug_print(anything) -> Nil = - "../gleam_stdlib" "log" + "../gleam_stdlib.js" "log" } if erlang { @@ -77,6 +96,7 @@ if erlang { NoData } + // TODO: move to OS library /// Reads a line from standard input with the given prompt. /// /// # Example @@ -88,3 +108,10 @@ if erlang { pub external fn get_line(prompt: String) -> Result(String, GetLineError) = "gleam_stdlib" "get_line" } + +if erlang { + external type DoNotLeak + + external fn erl_print(String, List(a)) -> DoNotLeak = + "io" "fwrite" +} diff --git a/src/gleam/string.gleam b/src/gleam/string.gleam index 7cf506b..545d1e4 100644 --- a/src/gleam/string.gleam +++ b/src/gleam/string.gleam @@ -296,8 +296,8 @@ if erlang { fn do_contains(haystack: String, needle: String) -> Bool { haystack |> erl_contains(needle) - |> dynamic.atom - |> result.is_error + |> dynamic.bit_string + |> result.is_ok } } diff --git a/src/gleam_stdlib.erl b/src/gleam_stdlib.erl index 49a3b0c..ff8b97b 100644 --- a/src/gleam_stdlib.erl +++ b/src/gleam_stdlib.erl @@ -2,14 +2,13 @@ -include_lib("eunit/include/eunit.hrl"). -export([should_equal/2, should_not_equal/2, should_be_ok/1, should_be_error/1, - atom_from_string/1, atom_create_from_string/1, atom_to_string/1, map_get/2, iodata_append/2, identity/1, decode_int/1, decode_bool/1, - decode_float/1, decode_thunk/1, decode_atom/1, decode_list/1, + decode_float/1, decode_thunk/1, decode_list/1, decode_optional/2, decode_field/2, decode_element/2, parse_int/1, parse_float/1, less_than/2, string_pop_grapheme/1, string_starts_with/2, string_ends_with/2, string_pad/4, decode_tuple2/1, decode_tuple3/1, decode_tuple4/1, decode_tuple5/1, decode_tuple6/1, decode_map/1, - bit_string_int_to_u32/1, bit_string_int_from_u32/1, + bit_string_int_to_u32/1, bit_string_int_from_u32/1, decode_result/1, bit_string_append/2, bit_string_part_/3, decode_bit_string/1, compile_regex/2, regex_match/2, regex_split/2, regex_scan/2, base_decode64/1, wrap_list/1, get_line/1]). @@ -33,17 +32,6 @@ map_get(Map, Key) -> OkFound -> OkFound end. -atom_create_from_string(S) -> - binary_to_atom(S, utf8). - -atom_to_string(S) -> - atom_to_binary(S, utf8). - -atom_from_string(S) -> - try {ok, binary_to_existing_atom(S, utf8)} - catch error:badarg -> {error, atom_not_loaded} - end. - iodata_append(Iodata, String) -> [Iodata, String]. identity(X) -> X. @@ -79,9 +67,6 @@ decode_tuple6(Data) -> decode_error_msg("a 6 element tuple", Data). decode_map(Data) when is_map(Data) -> {ok, Data}; decode_map(Data) -> decode_error_msg("a map", Data). -decode_atom(Data) when is_atom(Data) -> {ok, Data}; -decode_atom(Data) -> decode_error_msg("an atom", Data). - decode_bit_string(Data) when is_bitstring(Data) -> {ok, Data}; decode_bit_string(Data) -> decode_error_msg("a bit_string", Data). @@ -119,6 +104,32 @@ decode_element(Data, Position) when is_tuple(Data) -> end; decode_element(Data, _Position) -> decode_error_msg("a tuple", Data). +decode_optional(Term, F) -> + Decode = fun(Inner) -> + case F(Inner) of + {ok, Decoded} -> {ok, {some, Decoded}}; + Error -> Error + end + end, + case Term of + undefined -> {ok, none}; + error -> {ok, none}; + null -> {ok, none}; + none -> {ok, none}; + nil -> {ok, none}; + {some, Inner} -> Decode(Inner); + _ -> Decode(Term) + end. + +decode_result(Term) -> + case Term of + {ok, Inner} -> {ok, {ok, Inner}}; + ok -> {ok, {ok, nil}}; + {error, Inner} -> {ok, {error, Inner}}; + error -> {ok, {error, nil}}; + _ -> decode_error_msg("a result tuple", Term) + end. + parse_int(String) -> case catch binary_to_integer(String) of Int when is_integer(Int) -> {ok, Int}; diff --git a/src/gleam_stdlib.js b/src/gleam_stdlib.js index 68ab895..1e93901 100644 --- a/src/gleam_stdlib.js +++ b/src/gleam_stdlib.js @@ -199,3 +199,11 @@ export function bit_string_to_string(bit_string) { return gleam_error(undefined); } } + +export function print(string) { + if (typeof process === "object") { + process.stdout.write(string); // We can write without a trailing newline + } else { + console.log(string); // We're in a browser. Newlines are mandated + } +} diff --git a/test/gleam/atom_test.gleam b/test/gleam/atom_test.gleam deleted file mode 100644 index ccc34ed..0000000 --- a/test/gleam/atom_test.gleam +++ /dev/null @@ -1,47 +0,0 @@ -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/dynamic_test.gleam b/test/gleam/dynamic_test.gleam index ca82802..fee2539 100644 --- a/test/gleam/dynamic_test.gleam +++ b/test/gleam/dynamic_test.gleam @@ -1,7 +1,6 @@ if erlang { import gleam/bit_string import gleam/dynamic - import gleam/atom import gleam/list import gleam/should import gleam/result @@ -156,30 +155,6 @@ if erlang { |> 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 @@ -222,48 +197,50 @@ if erlang { |> should.be_error } - pub fn option_test() { - let Ok(null) = atom.from_string("null") - + pub fn optional_test() { 1 |> dynamic.from - |> dynamic.option(dynamic.int) + |> dynamic.optional(dynamic.int) |> should.equal(Ok(Some(1))) - null + + option.None + |> dynamic.from + |> dynamic.optional(dynamic.int) + |> should.equal(Ok(None)) + + Nil |> dynamic.from - |> dynamic.option(dynamic.int) + |> dynamic.optional(dynamic.int) |> should.equal(Ok(None)) + 1 |> dynamic.from - |> dynamic.option(dynamic.string) + |> dynamic.optional(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) + |> map.insert("ok", 1) |> dynamic.from - |> dynamic.field(ok_atom) + |> dynamic.field("ok") |> should.equal(Ok(dynamic.from(1))) map.new() - |> map.insert(ok_atom, 3) - |> map.insert(error_atom, 1) + |> map.insert("ok", 3) + |> map.insert("error", 1) |> dynamic.from - |> dynamic.field(ok_atom) + |> dynamic.field("ok") |> should.equal(Ok(dynamic.from(3))) map.new() |> dynamic.from - |> dynamic.field(ok_atom) + |> dynamic.field("ok") |> should.be_error 1 |> dynamic.from - |> dynamic.field(ok_atom) + |> dynamic.field("ok") |> should.be_error [] @@ -273,13 +250,12 @@ if erlang { } pub fn element_test() { - let Ok(ok_atom) = atom.from_string("ok") - let ok_one_tuple = #(ok_atom, 1) + let ok_one_tuple = #("ok", 1) ok_one_tuple |> dynamic.from |> dynamic.element(0) - |> should.equal(Ok(dynamic.from(ok_atom))) + |> should.equal(Ok(dynamic.from("ok"))) ok_one_tuple |> dynamic.from @@ -302,7 +278,7 @@ if erlang { |> should.be_error map.new() - |> map.insert(1, ok_atom) + |> map.insert(1, "ok") |> dynamic.from |> dynamic.element(0) |> should.be_error @@ -707,14 +683,12 @@ if erlang { 1 |> dynamic.from |> dynamic.result - |> should.equal(Error("Expected a 2 element tuple, got an int")) - - let tag = atom.create_from_string("bad") + |> should.equal(Error("Expected a result tuple, got an int")) - #(tag, "value") + #("bad", "value") |> dynamic.from |> dynamic.result - |> should.equal(Error("Expected a tag of \"ok\" or \"error\", got \"bad\"")) + |> should.equal(Error("Expected a result tuple, got a 2 element tuple")) } pub fn typed_result_test() { @@ -741,6 +715,6 @@ if erlang { 1 |> dynamic.from |> dynamic.typed_result(ok: dynamic.int, error: dynamic.string) - |> should.equal(Error("Expected a 2 element tuple, got an int")) + |> should.equal(Error("Expected a result tuple, got an int")) } } |