aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLouis Pilfold <louis@lpil.uk>2021-07-31 11:26:38 +0100
committerLouis Pilfold <louis@lpil.uk>2021-07-31 11:26:38 +0100
commitf78920f014e2e2b3d512bd5703902bbdba68eb4f (patch)
treea8dbc823a3223fb67f2d8e34ef4287f4de3f161d
parent942521b3016af2f24bff4eb1237c01a7856ffea5 (diff)
downloadgleam_stdlib-f78920f014e2e2b3d512bd5703902bbdba68eb4f.tar.gz
gleam_stdlib-f78920f014e2e2b3d512bd5703902bbdba68eb4f.zip
Move atom module to gleam_erlang library
-rw-r--r--CHANGELOG.md5
-rw-r--r--src/gleam/atom.gleam64
-rw-r--r--src/gleam/dynamic.gleam73
-rw-r--r--src/gleam/io.gleam75
-rw-r--r--src/gleam/string.gleam4
-rw-r--r--src/gleam_stdlib.erl45
-rw-r--r--src/gleam_stdlib.js8
-rw-r--r--test/gleam/atom_test.gleam47
-rw-r--r--test/gleam/dynamic_test.gleam78
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"))
}
}