diff --git a/README.md b/README.md index fe797e1..55dcc85 100644 --- a/README.md +++ b/README.md @@ -153,6 +153,8 @@ Fine provides implementations for the following types: | `std::vector` | x | x | `list(a)` | | `std::map` | x | x | `%{k => v}` | | `std::unordered_map` | x | x | `%{k => v}` | +| `std::multimap` | x | x | `list({k, v})` | +| `std::unordered_multimap` | x | x | `list({k, v})` | | `fine::ResourcePtr` | x | x | `reference` | | `T` with [struct metadata](#structs) | x | x | `%a{}` | | `fine::Ok` | x | | `{:ok, ...}` | diff --git a/c_include/fine.hpp b/c_include/fine.hpp index 7a31677..0041d3f 100644 --- a/c_include/fine.hpp +++ b/c_include/fine.hpp @@ -381,14 +381,14 @@ inline Term make_new_binary(ErlNifEnv *env, const char *data, size_t size) { // // The given type must have a specialized Decoder implementation. template T decode(ErlNifEnv *env, const ERL_NIF_TERM &term) { - return Decoder::decode(env, term); + return Decoder>::decode(env, term); } // Encodes the given value as a Erlang term. // // The value type must have a specialized Encoder implementation. template ERL_NIF_TERM encode(ErlNifEnv *env, const T &value) { - return Encoder::encode(env, value); + return Encoder>::encode(env, value); } // We want decode to return the value, and since the argument types @@ -473,9 +473,10 @@ template <> struct Decoder { } if (!enif_get_local_pid(env, term, &pid)) { // If the term is a PID and it is not local, it means it's a remote PID. - throw std::invalid_argument( - "decode failed, expected a local pid, but got a remote one. NIFs can " - "only send messages to local PIDs and remote PIDs cannot be decoded"); + throw std::invalid_argument("decode failed, expected a local pid, but " + "got a remote one. NIFs can " + "only send messages to local PIDs and " + "remote PIDs cannot be decoded"); } return pid; } @@ -591,6 +592,25 @@ template struct Decoder> { } }; +template struct Decoder> { + static std::pair decode(ErlNifEnv *env, const ERL_NIF_TERM &term) { + int size; + const ERL_NIF_TERM *terms; + if (!enif_get_tuple(env, term, &size, &terms)) { + throw std::invalid_argument("decode failed, expected a tuple"); + } + + if (size != 2) { + throw std::invalid_argument( + "decode failed, expected tuple to have 2 elements, but had " + + std::to_string(size)); + } + + return std::make_pair(fine::decode(env, terms[0]), + fine::decode(env, terms[1])); + } +}; + template struct Decoder> { static std::vector decode(ErlNifEnv *env, const ERL_NIF_TERM &term) { @@ -690,6 +710,60 @@ struct Decoder> { }; }; +template +struct Decoder> { + static std::multimap decode(ErlNifEnv *env, + const ERL_NIF_TERM &term) { + unsigned int length; + + if (!enif_get_list_length(env, term, &length)) { + throw std::invalid_argument("decode failed, expected a list"); + } + + std::multimap map; + + auto list = term; + + ERL_NIF_TERM head, tail; + while (enif_get_list_cell(env, list, &head, &tail)) { + auto entry = fine::decode>(env, head); + + map.emplace(std::move(entry)); + + list = tail; + } + + return map; + } +}; + +template +struct Decoder> { + static std::unordered_multimap + decode(ErlNifEnv *env, const ERL_NIF_TERM &term) { + unsigned int length; + + if (!enif_get_list_length(env, term, &length)) { + throw std::invalid_argument("decode failed, expected a list"); + } + + std::unordered_multimap map; + + auto list = term; + + ERL_NIF_TERM head, tail; + while (enif_get_list_cell(env, list, &head, &tail)) { + auto entry = fine::decode>(env, head); + + map.emplace(std::move(entry)); + + list = tail; + } + + return map; + } +}; + template struct Decoder> { static ResourcePtr decode(ErlNifEnv *env, const ERL_NIF_TERM &term) { void *ptr; @@ -892,6 +966,14 @@ template struct Encoder> { } }; +template struct Encoder> { + static ERL_NIF_TERM encode(ErlNifEnv *env, const std::pair &pair) { + const auto first = fine::encode(env, pair.first); + const auto second = fine::encode(env, pair.second); + return enif_make_tuple(env, 2, first, second); + } +}; + template struct Encoder> { static ERL_NIF_TERM encode(ErlNifEnv *env, const std::vector &vector) { @@ -956,6 +1038,39 @@ struct Encoder> { } }; +template +struct Encoder> { + static ERL_NIF_TERM encode(ErlNifEnv *env, + const std::multimap &map) { + auto terms = std::vector(); + terms.reserve(map.size()); + + for (const auto &entry : map) { + terms.emplace_back(fine::encode(env, entry)); + } + + return enif_make_list_from_array(env, terms.data(), + static_cast(terms.size())); + } +}; + +template +struct Encoder> { + static ERL_NIF_TERM + encode(ErlNifEnv *env, + const std::unordered_multimap &map) { + auto terms = std::vector(); + terms.reserve(map.size()); + + for (const auto &entry : map) { + terms.emplace_back(fine::encode(env, entry)); + } + + return enif_make_list_from_array(env, terms.data(), + static_cast(terms.size())); + } +}; + template struct Encoder> { static ERL_NIF_TERM encode(ErlNifEnv *env, const ResourcePtr &resource) { return enif_make_resource(env, reinterpret_cast(resource.get())); diff --git a/test/c_src/finest.cpp b/test/c_src/finest.cpp index 8f1f6d6..c04ef4f 100644 --- a/test/c_src/finest.cpp +++ b/test/c_src/finest.cpp @@ -205,6 +205,7 @@ codec_map_atom_int64(ErlNifEnv *, std::map term) { return term; } FINE_NIF(codec_map_atom_int64, 0); + std::map, std::pmr::polymorphic_allocator>> codec_map_atom_int64_alloc( @@ -237,6 +238,46 @@ codec_unordered_map_atom_int64_alloc( } FINE_NIF(codec_unordered_map_atom_int64_alloc, 0); +std::multimap +codec_multimap_atom_int64(ErlNifEnv *, + std::multimap term) { + return term; +} +FINE_NIF(codec_multimap_atom_int64, 0); + +std::multimap< + fine::Atom, int64_t, std::less, + std::pmr::polymorphic_allocator>> +codec_multimap_atom_int64_alloc( + ErlNifEnv *, + std::multimap< + fine::Atom, int64_t, std::less, + std::pmr::polymorphic_allocator>> + term) { + return term; +} +FINE_NIF(codec_multimap_atom_int64_alloc, 0); + +std::unordered_multimap +codec_unordered_multimap_atom_int64( + ErlNifEnv *, std::unordered_multimap term) { + return term; +} +FINE_NIF(codec_unordered_multimap_atom_int64, 0); + +std::unordered_multimap< + fine::Atom, int64_t, std::hash, std::equal_to, + std::pmr::polymorphic_allocator>> +codec_unordered_multimap_atom_int64_alloc( + ErlNifEnv *, + std::unordered_multimap< + fine::Atom, int64_t, std::hash, std::equal_to, + std::pmr::polymorphic_allocator>> + term) { + return term; +} +FINE_NIF(codec_unordered_multimap_atom_int64_alloc, 0); + fine::ResourcePtr codec_resource(ErlNifEnv *, fine::ResourcePtr term) { return term; diff --git a/test/lib/finest/nif.ex b/test/lib/finest/nif.ex index 0e97d5d..6f3426a 100644 --- a/test/lib/finest/nif.ex +++ b/test/lib/finest/nif.ex @@ -40,6 +40,10 @@ defmodule Finest.NIF do def codec_map_atom_int64_alloc(_term), do: err!() def codec_unordered_map_atom_int64(_term), do: err!() def codec_unordered_map_atom_int64_alloc(_term), do: err!() + def codec_multimap_atom_int64(_term), do: err!() + def codec_multimap_atom_int64_alloc(_term), do: err!() + def codec_unordered_multimap_atom_int64(_term), do: err!() + def codec_unordered_multimap_atom_int64_alloc(_term), do: err!() def codec_resource(_term), do: err!() def codec_struct(_term), do: err!() def codec_struct_exception(_term), do: err!() diff --git a/test/test/finest_test.exs b/test/test/finest_test.exs index 37a1a90..6570c78 100644 --- a/test/test/finest_test.exs +++ b/test/test/finest_test.exs @@ -233,6 +233,78 @@ defmodule FinestTest do end end + test "keyword" do + empty_keyword = [] + + small_keyword = [hello: 1, world: 2] + + large_keyword = + 0..64 |> Enum.map(fn x -> {:"a#{x}", x} end) |> Enum.to_list() + + for keyword <- [empty_keyword, small_keyword, large_keyword] do + assert Enum.sort(NIF.codec_multimap_atom_int64(keyword)) == Enum.sort(keyword) + assert Enum.sort(NIF.codec_multimap_atom_int64_alloc(keyword)) == Enum.sort(keyword) + assert Enum.sort(NIF.codec_unordered_multimap_atom_int64(keyword)) == Enum.sort(keyword) + + assert Enum.sort(NIF.codec_unordered_multimap_atom_int64_alloc(keyword)) == + Enum.sort(keyword) + end + + invalid_keyword = 10 + + assert_raise ArgumentError, "decode failed, expected a list", fn -> + NIF.codec_multimap_atom_int64(invalid_keyword) + end + + assert_raise ArgumentError, "decode failed, expected a list", fn -> + NIF.codec_multimap_atom_int64_alloc(invalid_keyword) + end + + assert_raise ArgumentError, "decode failed, expected a list", fn -> + NIF.codec_unordered_multimap_atom_int64(invalid_keyword) + end + + assert_raise ArgumentError, "decode failed, expected a list", fn -> + NIF.codec_unordered_multimap_atom_int64_alloc(invalid_keyword) + end + + keyword_with_invalid_key = [{"hello", 42}] + + assert_raise ArgumentError, "decode failed, expected an atom", fn -> + NIF.codec_multimap_atom_int64(keyword_with_invalid_key) + end + + assert_raise ArgumentError, "decode failed, expected an atom", fn -> + NIF.codec_multimap_atom_int64_alloc(keyword_with_invalid_key) + end + + assert_raise ArgumentError, "decode failed, expected an atom", fn -> + NIF.codec_unordered_multimap_atom_int64(keyword_with_invalid_key) + end + + assert_raise ArgumentError, "decode failed, expected an atom", fn -> + NIF.codec_unordered_multimap_atom_int64_alloc(keyword_with_invalid_key) + end + + keyword_with_invalid_value = [hello: 1.0] + + assert_raise ArgumentError, "decode failed, expected an integer", fn -> + NIF.codec_multimap_atom_int64(keyword_with_invalid_value) + end + + assert_raise ArgumentError, "decode failed, expected an integer", fn -> + NIF.codec_multimap_atom_int64_alloc(keyword_with_invalid_value) + end + + assert_raise ArgumentError, "decode failed, expected an integer", fn -> + NIF.codec_unordered_multimap_atom_int64(keyword_with_invalid_value) + end + + assert_raise ArgumentError, "decode failed, expected an integer", fn -> + NIF.codec_unordered_multimap_atom_int64_alloc(keyword_with_invalid_value) + end + end + test "resource" do resource = NIF.resource_create(self()) assert is_reference(resource)