aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLouis Pilfold <louis@lpil.uk>2021-09-09 22:44:35 +0100
committerLouis Pilfold <louis@lpil.uk>2021-09-09 22:44:35 +0100
commitba5f9b8c97ff95eb03304ff42628c5af77d56d95 (patch)
treede165c53b3b3003e3bc2d1bf8aee8a6f5f64f25c
parenta595d4be50fe7e4e5a946bf5ed5878fa7bd42b64 (diff)
downloadgleam_stdlib-ba5f9b8c97ff95eb03304ff42628c5af77d56d95.tar.gz
gleam_stdlib-ba5f9b8c97ff95eb03304ff42628c5af77d56d95.zip
Move tuple decoding into Gleam
-rw-r--r--src/gleam/dynamic.gleam72
-rw-r--r--src/gleam_stdlib.erl68
-rw-r--r--src/gleam_stdlib.js29
-rw-r--r--test/gleam/dynamic_test.gleam15
4 files changed, 101 insertions, 83 deletions
diff --git a/src/gleam/dynamic.gleam b/src/gleam/dynamic.gleam
index d494550..860496d 100644
--- a/src/gleam/dynamic.gleam
+++ b/src/gleam/dynamic.gleam
@@ -1,6 +1,7 @@
import gleam/bit_string
import gleam/list
import gleam/map
+import gleam/int
import gleam/option
import gleam/result
import gleam/string_builder
@@ -118,6 +119,27 @@ if javascript {
"../gleam_stdlib.js" "decode_string"
}
+/// Return a string indicating the type of the dynamic value.
+///
+/// ```
+/// > classify(from("Hello"))
+/// "String"
+/// ```
+///
+pub fn classify(data: Dynamic) -> String {
+ do_classify(data)
+}
+
+if erlang {
+ external fn do_classify(Dynamic) -> String =
+ "gleam_stdlib" "classify_dynamic"
+}
+
+if javascript {
+ external fn do_classify(Dynamic) -> String =
+ "../gleam_stdlib.js" "classify_dynamic"
+}
+
/// Checks to see whether a Dynamic value is an int, and return the int if it
/// is.
///
@@ -361,17 +383,57 @@ pub fn element(
from data: Dynamic,
get index: Int,
) -> Result(Dynamic, DecodeError) {
- decode_element(data, index)
+ try tuple = decode_tuple(data)
+ let size = tuple_size(tuple)
+ case index >= 0 {
+ True ->
+ case index < size {
+ True -> tuple_get(tuple, index)
+ False -> decode_tuple_error(index + 1, data)
+ }
+ False ->
+ case int.absolute_value(index) <= size {
+ True -> tuple_get(tuple, size + index)
+ False -> decode_tuple_error(int.absolute_value(index), data)
+ }
+ }
+}
+
+fn decode_tuple_error(size: Int, data: Dynamic) -> Result(a, DecodeError) {
+ let s = case size {
+ 0 -> ""
+ _ -> "s"
+ }
+ ["Tuple of at least ", int.to_string(size), " element", s]
+ |> string_builder.from_strings
+ |> string_builder.to_string
+ |> DecodeError(found: classify(data))
+ |> Error
}
+// A tuple of unknown size
+external type UnknownTuple
+
if erlang {
- external fn decode_element(Dynamic, Int) -> Result(a, DecodeError) =
- "gleam_stdlib" "decode_element"
+ external fn decode_tuple(Dynamic) -> Result(UnknownTuple, DecodeError) =
+ "gleam_stdlib" "decode_tuple"
+
+ external fn tuple_get(UnknownTuple, Int) -> Result(Dynamic, DecodeError) =
+ "gleam_stdlib" "tuple_get"
+
+ external fn tuple_size(UnknownTuple) -> Int =
+ "gleam_stdlib" "size_of_tuple"
}
if javascript {
- external fn decode_element(Dynamic, Int) -> Result(a, DecodeError) =
- "../gleam_stdlib.js" "decode_element"
+ external fn decode_tuple(Dynamic) -> Result(UnknownTuple, DecodeError) =
+ "../gleam_stdlib.js" "decode_tuple"
+
+ external fn tuple_get(UnknownTuple, Int) -> Result(Dynamic, DecodeError) =
+ "../gleam_stdlib.js" "tuple_get"
+
+ external fn tuple_size(UnknownTuple) -> Int =
+ "../gleam_stdlib.js" "length"
}
if erlang {
diff --git a/src/gleam_stdlib.erl b/src/gleam_stdlib.erl
index c52e694..2d7345d 100644
--- a/src/gleam_stdlib.erl
+++ b/src/gleam_stdlib.erl
@@ -4,14 +4,15 @@
-export([should_equal/2, should_not_equal/2, should_be_ok/1, should_be_error/1,
map_get/2, iodata_append/2, identity/1, decode_int/1, decode_bool/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, wrap_list/1,
+ decode_field/2, parse_int/1, parse_float/1, less_than/2,
+ string_pop_grapheme/1, string_starts_with/2, wrap_list/1,
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, decode_result/1,
bit_string_slice/3, decode_bit_string/1, compile_regex/2, regex_scan/2,
percent_encode/1, percent_decode/1, regex_check/2, regex_split/2,
- base_decode64/1, parse_query/1, bit_string_concat/1]).
+ base_decode64/1, parse_query/1, bit_string_concat/1, size_of_tuple/1,
+ decode_tuple/1, tuple_get/2, classify_dynamic/1]).
%% Taken from OTP's uri_string module
-define(DEC2HEX(X),
@@ -50,25 +51,25 @@ iodata_append(Iodata, String) -> [Iodata, String].
identity(X) -> X.
decode_error_msg(Expected, Data) ->
- {error, {decode_error, Expected, classify(Data)}}.
-
-classify(X) when is_atom(X) -> <<"Atom">>;
-classify(X) when is_binary(X) -> <<"String">>;
-classify(X) when is_bitstring(X) -> <<"BitString">>;
-classify(X) when is_integer(X) -> <<"Int">>;
-classify(X) when is_float(X) -> <<"Float">>;
-classify(X) when is_list(X) -> <<"List">>;
-classify(X) when is_boolean(X) -> <<"Bool">>;
-classify(X) when is_map(X) -> <<"Map">>;
-classify(X) when is_tuple(X) ->
+ {error, {decode_error, Expected, classify_dynamic(Data)}}.
+
+classify_dynamic(X) when is_atom(X) -> <<"Atom">>;
+classify_dynamic(X) when is_binary(X) -> <<"String">>;
+classify_dynamic(X) when is_bitstring(X) -> <<"BitString">>;
+classify_dynamic(X) when is_integer(X) -> <<"Int">>;
+classify_dynamic(X) when is_float(X) -> <<"Float">>;
+classify_dynamic(X) when is_list(X) -> <<"List">>;
+classify_dynamic(X) when is_boolean(X) -> <<"Bool">>;
+classify_dynamic(X) when is_map(X) -> <<"Map">>;
+classify_dynamic(X) when is_tuple(X) ->
iolist_to_binary(["Tuple of ", integer_to_list(tuple_size(X)), " elements"]);
-classify(X) when
+classify_dynamic(X) when
is_function(X, 0) orelse is_function(X, 1) orelse is_function(X, 2) orelse
is_function(X, 3) orelse is_function(X, 4) orelse is_function(X, 5) orelse
is_function(X, 6) orelse is_function(X, 7) orelse is_function(X, 8) orelse
is_function(X, 9) orelse is_function(X, 10) orelse is_function(X, 11) orelse
is_function(X, 12) -> <<"Function">>;
-classify(_) -> <<"Some other type">>.
+classify_dynamic(_) -> <<"Some other type">>.
decode_tuple2({_, _} = T) -> {ok, T};
decode_tuple2(Data) -> decode_error_msg(<<"Tuple of 2 elements">>, Data).
@@ -112,35 +113,14 @@ decode_field(Data, Key) ->
_ -> decode_error_msg(io_lib:format("a map with key `~p`", [Key]), Data)
end.
+size_of_tuple(Data) -> tuple_size(Data).
-decode_tuple_error(Size, Data) ->
- S = case Size of
- 1 -> "";
- _ -> "s"
- end,
- Msg = list_to_binary([
- "Tuple of at least ", integer_to_list(Size), " element", S
- ]),
- decode_error_msg(Msg, Data).
-
-decode_element(Data, Index) when is_tuple(Data) ->
- Size = tuple_size(Data),
- case Index >= 0 of
- true -> case Index < Size of
- true -> {ok, element(Index + 1, Data)};
- false -> decode_tuple_error(Index + 1, Data)
- end;
- false -> case abs(Index) < Size of
- true -> {ok, element(Size + Index + 1, Data)};
- false -> decode_tuple_error(abs(Index), Data)
- end
- end;
-decode_element(Data, Index) ->
- Size = case Index < 0 of
- true -> abs(Index);
- false -> Index + 1
- end,
- decode_tuple_error(Size, Data).
+tuple_get(_tup, Index) when Index < 0 -> {error, nil};
+tuple_get(Data, Index) when Index >= tuple_size(Data) -> {error, nil};
+tuple_get(Data, Index) -> {ok, element(Index + 1, Data)}.
+
+decode_tuple(Data) when is_tuple(Data) -> {ok, Data};
+decode_tuple(Data) -> decode_error_msg(<<"Tuple">>, Data).
decode_optional(Term, F) ->
Decode = fun(Inner) ->
diff --git a/src/gleam_stdlib.js b/src/gleam_stdlib.js
index b743c03..47801ce 100644
--- a/src/gleam_stdlib.js
+++ b/src/gleam_stdlib.js
@@ -469,7 +469,7 @@ export function decode64(sBase64) {
return new Ok(new BitString(taBytes));
}
-function classify_dynamic(data) {
+export function classify_dynamic(data) {
if (typeof data === "string") {
return "String";
} else if (List.isList(data)) {
@@ -518,27 +518,12 @@ export function decode_bit_string(data) {
: decoder_error("BitString", data);
}
-function decode_tuple_error(size, data) {
- return decoder_error(
- `Tuple of at least ${size} element${size == 1 ? "" : "s"}`,
- data
- );
+export function decode_tuple(data) {
+ return Array.isArray(data) ? new Ok(data) : decoder_error("Tuple", data);
}
-export function decode_element(data, index) {
- if (!Array.isArray(data))
- return decode_tuple_error(index < 0 ? Math.abs(index) : index + 1, data);
- if (index >= 0) {
- if (index < data.length) {
- return new Ok(data[index]);
- } else {
- return decode_tuple_error(index + 1, data);
- }
- } else {
- if (Math.abs(index) <= data.length) {
- return new Ok(data[data.length + index]);
- } else {
- return decode_tuple_error(Math.abs(index), data);
- }
- }
+export function tuple_get(data, index) {
+ return index >= 0 && data.length > index
+ ? new Ok(data[index])
+ : new Error(Nil);
}
diff --git a/test/gleam/dynamic_test.gleam b/test/gleam/dynamic_test.gleam
index 21fb98c..3585cda 100644
--- a/test/gleam/dynamic_test.gleam
+++ b/test/gleam/dynamic_test.gleam
@@ -293,27 +293,18 @@ pub fn element_test() {
1
|> dynamic.from
|> dynamic.element(-3)
- |> should.equal(Error(DecodeError(
- expected: "Tuple of at least 3 elements",
- found: "Int",
- )))
+ |> should.equal(Error(DecodeError(expected: "Tuple", found: "Int")))
1
|> dynamic.from
|> dynamic.element(0)
- |> should.equal(Error(DecodeError(
- expected: "Tuple of at least 1 element",
- found: "Int",
- )))
+ |> should.equal(Error(DecodeError(expected: "Tuple", found: "Int")))
map.new()
|> map.insert(1, "ok")
|> dynamic.from
|> dynamic.element(0)
- |> should.equal(Error(DecodeError(
- expected: "Tuple of at least 1 element",
- found: "Map",
- )))
+ |> should.equal(Error(DecodeError(expected: "Tuple", found: "Map")))
}
if erlang {