diff options
author | Louis Pilfold <louis@lpil.uk> | 2021-09-09 22:44:35 +0100 |
---|---|---|
committer | Louis Pilfold <louis@lpil.uk> | 2021-09-09 22:44:35 +0100 |
commit | ba5f9b8c97ff95eb03304ff42628c5af77d56d95 (patch) | |
tree | de165c53b3b3003e3bc2d1bf8aee8a6f5f64f25c | |
parent | a595d4be50fe7e4e5a946bf5ed5878fa7bd42b64 (diff) | |
download | gleam_stdlib-ba5f9b8c97ff95eb03304ff42628c5af77d56d95.tar.gz gleam_stdlib-ba5f9b8c97ff95eb03304ff42628c5af77d56d95.zip |
Move tuple decoding into Gleam
-rw-r--r-- | src/gleam/dynamic.gleam | 72 | ||||
-rw-r--r-- | src/gleam_stdlib.erl | 68 | ||||
-rw-r--r-- | src/gleam_stdlib.js | 29 | ||||
-rw-r--r-- | test/gleam/dynamic_test.gleam | 15 |
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 { |