Skip to content

Commit dd85a34

Browse files
Support std::multimap and std::unordered_multimap (#15)
1 parent 1fb0297 commit dd85a34

File tree

5 files changed

+239
-5
lines changed

5 files changed

+239
-5
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,8 @@ Fine provides implementations for the following types:
153153
| `std::vector<T>` | x | x | `list(a)` |
154154
| `std::map<K, V>` | x | x | `%{k => v}` |
155155
| `std::unordered_map<K, V>` | x | x | `%{k => v}` |
156+
| `std::multimap<K, V>` | x | x | `list({k, v})` |
157+
| `std::unordered_multimap<K, V>` | x | x | `list({k, v})` |
156158
| `fine::ResourcePtr<T>` | x | x | `reference` |
157159
| `T` with [struct metadata](#structs) | x | x | `%a{}` |
158160
| `fine::Ok<Args...>` | x | | `{:ok, ...}` |

c_include/fine.hpp

Lines changed: 120 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -381,14 +381,14 @@ inline Term make_new_binary(ErlNifEnv *env, const char *data, size_t size) {
381381
//
382382
// The given type must have a specialized Decoder<T> implementation.
383383
template <typename T> T decode(ErlNifEnv *env, const ERL_NIF_TERM &term) {
384-
return Decoder<T>::decode(env, term);
384+
return Decoder<std::remove_cv_t<T>>::decode(env, term);
385385
}
386386

387387
// Encodes the given value as a Erlang term.
388388
//
389389
// The value type must have a specialized Encoder<T> implementation.
390390
template <typename T> ERL_NIF_TERM encode(ErlNifEnv *env, const T &value) {
391-
return Encoder<T>::encode(env, value);
391+
return Encoder<std::remove_cv_t<T>>::encode(env, value);
392392
}
393393

394394
// We want decode to return the value, and since the argument types
@@ -473,9 +473,10 @@ template <> struct Decoder<ErlNifPid> {
473473
}
474474
if (!enif_get_local_pid(env, term, &pid)) {
475475
// If the term is a PID and it is not local, it means it's a remote PID.
476-
throw std::invalid_argument(
477-
"decode failed, expected a local pid, but got a remote one. NIFs can "
478-
"only send messages to local PIDs and remote PIDs cannot be decoded");
476+
throw std::invalid_argument("decode failed, expected a local pid, but "
477+
"got a remote one. NIFs can "
478+
"only send messages to local PIDs and "
479+
"remote PIDs cannot be decoded");
479480
}
480481
return pid;
481482
}
@@ -591,6 +592,25 @@ template <typename... Args> struct Decoder<std::tuple<Args...>> {
591592
}
592593
};
593594

595+
template <typename T1, typename T2> struct Decoder<std::pair<T1, T2>> {
596+
static std::pair<T1, T2> decode(ErlNifEnv *env, const ERL_NIF_TERM &term) {
597+
int size;
598+
const ERL_NIF_TERM *terms;
599+
if (!enif_get_tuple(env, term, &size, &terms)) {
600+
throw std::invalid_argument("decode failed, expected a tuple");
601+
}
602+
603+
if (size != 2) {
604+
throw std::invalid_argument(
605+
"decode failed, expected tuple to have 2 elements, but had " +
606+
std::to_string(size));
607+
}
608+
609+
return std::make_pair(fine::decode<T1>(env, terms[0]),
610+
fine::decode<T2>(env, terms[1]));
611+
}
612+
};
613+
594614
template <typename T, typename Alloc> struct Decoder<std::vector<T, Alloc>> {
595615
static std::vector<T, Alloc> decode(ErlNifEnv *env,
596616
const ERL_NIF_TERM &term) {
@@ -690,6 +710,60 @@ struct Decoder<std::unordered_map<K, V, Hash, Pred, Alloc>> {
690710
};
691711
};
692712

713+
template <typename K, typename V, typename Compare, typename Alloc>
714+
struct Decoder<std::multimap<K, V, Compare, Alloc>> {
715+
static std::multimap<K, V, Compare, Alloc> decode(ErlNifEnv *env,
716+
const ERL_NIF_TERM &term) {
717+
unsigned int length;
718+
719+
if (!enif_get_list_length(env, term, &length)) {
720+
throw std::invalid_argument("decode failed, expected a list");
721+
}
722+
723+
std::multimap<K, V, Compare, Alloc> map;
724+
725+
auto list = term;
726+
727+
ERL_NIF_TERM head, tail;
728+
while (enif_get_list_cell(env, list, &head, &tail)) {
729+
auto entry = fine::decode<std::pair<const K, V>>(env, head);
730+
731+
map.emplace(std::move(entry));
732+
733+
list = tail;
734+
}
735+
736+
return map;
737+
}
738+
};
739+
740+
template <typename K, typename V, typename Hash, typename Pred, typename Alloc>
741+
struct Decoder<std::unordered_multimap<K, V, Hash, Pred, Alloc>> {
742+
static std::unordered_multimap<K, V, Hash, Pred, Alloc>
743+
decode(ErlNifEnv *env, const ERL_NIF_TERM &term) {
744+
unsigned int length;
745+
746+
if (!enif_get_list_length(env, term, &length)) {
747+
throw std::invalid_argument("decode failed, expected a list");
748+
}
749+
750+
std::unordered_multimap<K, V, Hash, Pred, Alloc> map;
751+
752+
auto list = term;
753+
754+
ERL_NIF_TERM head, tail;
755+
while (enif_get_list_cell(env, list, &head, &tail)) {
756+
auto entry = fine::decode<std::pair<const K, V>>(env, head);
757+
758+
map.emplace(std::move(entry));
759+
760+
list = tail;
761+
}
762+
763+
return map;
764+
}
765+
};
766+
693767
template <typename T> struct Decoder<ResourcePtr<T>> {
694768
static ResourcePtr<T> decode(ErlNifEnv *env, const ERL_NIF_TERM &term) {
695769
void *ptr;
@@ -892,6 +966,14 @@ template <typename... Args> struct Encoder<std::tuple<Args...>> {
892966
}
893967
};
894968

969+
template <typename T1, typename T2> struct Encoder<std::pair<T1, T2>> {
970+
static ERL_NIF_TERM encode(ErlNifEnv *env, const std::pair<T1, T2> &pair) {
971+
const auto first = fine::encode<T1>(env, pair.first);
972+
const auto second = fine::encode<T2>(env, pair.second);
973+
return enif_make_tuple(env, 2, first, second);
974+
}
975+
};
976+
895977
template <typename T, typename Alloc> struct Encoder<std::vector<T, Alloc>> {
896978
static ERL_NIF_TERM encode(ErlNifEnv *env,
897979
const std::vector<T, Alloc> &vector) {
@@ -956,6 +1038,39 @@ struct Encoder<std::unordered_map<K, V, Hash, Pred, Alloc>> {
9561038
}
9571039
};
9581040

1041+
template <typename K, typename V, typename Compare, typename Alloc>
1042+
struct Encoder<std::multimap<K, V, Compare, Alloc>> {
1043+
static ERL_NIF_TERM encode(ErlNifEnv *env,
1044+
const std::multimap<K, V, Compare, Alloc> &map) {
1045+
auto terms = std::vector<ERL_NIF_TERM>();
1046+
terms.reserve(map.size());
1047+
1048+
for (const auto &entry : map) {
1049+
terms.emplace_back(fine::encode(env, entry));
1050+
}
1051+
1052+
return enif_make_list_from_array(env, terms.data(),
1053+
static_cast<unsigned int>(terms.size()));
1054+
}
1055+
};
1056+
1057+
template <typename K, typename V, typename Hash, typename Pred, typename Alloc>
1058+
struct Encoder<std::unordered_multimap<K, V, Hash, Pred, Alloc>> {
1059+
static ERL_NIF_TERM
1060+
encode(ErlNifEnv *env,
1061+
const std::unordered_multimap<K, V, Hash, Pred, Alloc> &map) {
1062+
auto terms = std::vector<ERL_NIF_TERM>();
1063+
terms.reserve(map.size());
1064+
1065+
for (const auto &entry : map) {
1066+
terms.emplace_back(fine::encode(env, entry));
1067+
}
1068+
1069+
return enif_make_list_from_array(env, terms.data(),
1070+
static_cast<unsigned int>(terms.size()));
1071+
}
1072+
};
1073+
9591074
template <typename T> struct Encoder<ResourcePtr<T>> {
9601075
static ERL_NIF_TERM encode(ErlNifEnv *env, const ResourcePtr<T> &resource) {
9611076
return enif_make_resource(env, reinterpret_cast<void *>(resource.get()));

test/c_src/finest.cpp

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,7 @@ codec_map_atom_int64(ErlNifEnv *, std::map<fine::Atom, int64_t> term) {
205205
return term;
206206
}
207207
FINE_NIF(codec_map_atom_int64, 0);
208+
208209
std::map<fine::Atom, int64_t, std::less<fine::Atom>,
209210
std::pmr::polymorphic_allocator<std::pair<const fine::Atom, int64_t>>>
210211
codec_map_atom_int64_alloc(
@@ -237,6 +238,46 @@ codec_unordered_map_atom_int64_alloc(
237238
}
238239
FINE_NIF(codec_unordered_map_atom_int64_alloc, 0);
239240

241+
std::multimap<fine::Atom, int64_t>
242+
codec_multimap_atom_int64(ErlNifEnv *,
243+
std::multimap<fine::Atom, int64_t> term) {
244+
return term;
245+
}
246+
FINE_NIF(codec_multimap_atom_int64, 0);
247+
248+
std::multimap<
249+
fine::Atom, int64_t, std::less<fine::Atom>,
250+
std::pmr::polymorphic_allocator<std::pair<const fine::Atom, int64_t>>>
251+
codec_multimap_atom_int64_alloc(
252+
ErlNifEnv *,
253+
std::multimap<
254+
fine::Atom, int64_t, std::less<fine::Atom>,
255+
std::pmr::polymorphic_allocator<std::pair<const fine::Atom, int64_t>>>
256+
term) {
257+
return term;
258+
}
259+
FINE_NIF(codec_multimap_atom_int64_alloc, 0);
260+
261+
std::unordered_multimap<fine::Atom, int64_t>
262+
codec_unordered_multimap_atom_int64(
263+
ErlNifEnv *, std::unordered_multimap<fine::Atom, int64_t> term) {
264+
return term;
265+
}
266+
FINE_NIF(codec_unordered_multimap_atom_int64, 0);
267+
268+
std::unordered_multimap<
269+
fine::Atom, int64_t, std::hash<fine::Atom>, std::equal_to<fine::Atom>,
270+
std::pmr::polymorphic_allocator<std::pair<const fine::Atom, int64_t>>>
271+
codec_unordered_multimap_atom_int64_alloc(
272+
ErlNifEnv *,
273+
std::unordered_multimap<
274+
fine::Atom, int64_t, std::hash<fine::Atom>, std::equal_to<fine::Atom>,
275+
std::pmr::polymorphic_allocator<std::pair<const fine::Atom, int64_t>>>
276+
term) {
277+
return term;
278+
}
279+
FINE_NIF(codec_unordered_multimap_atom_int64_alloc, 0);
280+
240281
fine::ResourcePtr<TestResource>
241282
codec_resource(ErlNifEnv *, fine::ResourcePtr<TestResource> term) {
242283
return term;

test/lib/finest/nif.ex

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@ defmodule Finest.NIF do
4040
def codec_map_atom_int64_alloc(_term), do: err!()
4141
def codec_unordered_map_atom_int64(_term), do: err!()
4242
def codec_unordered_map_atom_int64_alloc(_term), do: err!()
43+
def codec_multimap_atom_int64(_term), do: err!()
44+
def codec_multimap_atom_int64_alloc(_term), do: err!()
45+
def codec_unordered_multimap_atom_int64(_term), do: err!()
46+
def codec_unordered_multimap_atom_int64_alloc(_term), do: err!()
4347
def codec_resource(_term), do: err!()
4448
def codec_struct(_term), do: err!()
4549
def codec_struct_exception(_term), do: err!()

test/test/finest_test.exs

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,78 @@ defmodule FinestTest do
233233
end
234234
end
235235

236+
test "keyword" do
237+
empty_keyword = []
238+
239+
small_keyword = [hello: 1, world: 2]
240+
241+
large_keyword =
242+
0..64 |> Enum.map(fn x -> {:"a#{x}", x} end) |> Enum.to_list()
243+
244+
for keyword <- [empty_keyword, small_keyword, large_keyword] do
245+
assert Enum.sort(NIF.codec_multimap_atom_int64(keyword)) == Enum.sort(keyword)
246+
assert Enum.sort(NIF.codec_multimap_atom_int64_alloc(keyword)) == Enum.sort(keyword)
247+
assert Enum.sort(NIF.codec_unordered_multimap_atom_int64(keyword)) == Enum.sort(keyword)
248+
249+
assert Enum.sort(NIF.codec_unordered_multimap_atom_int64_alloc(keyword)) ==
250+
Enum.sort(keyword)
251+
end
252+
253+
invalid_keyword = 10
254+
255+
assert_raise ArgumentError, "decode failed, expected a list", fn ->
256+
NIF.codec_multimap_atom_int64(invalid_keyword)
257+
end
258+
259+
assert_raise ArgumentError, "decode failed, expected a list", fn ->
260+
NIF.codec_multimap_atom_int64_alloc(invalid_keyword)
261+
end
262+
263+
assert_raise ArgumentError, "decode failed, expected a list", fn ->
264+
NIF.codec_unordered_multimap_atom_int64(invalid_keyword)
265+
end
266+
267+
assert_raise ArgumentError, "decode failed, expected a list", fn ->
268+
NIF.codec_unordered_multimap_atom_int64_alloc(invalid_keyword)
269+
end
270+
271+
keyword_with_invalid_key = [{"hello", 42}]
272+
273+
assert_raise ArgumentError, "decode failed, expected an atom", fn ->
274+
NIF.codec_multimap_atom_int64(keyword_with_invalid_key)
275+
end
276+
277+
assert_raise ArgumentError, "decode failed, expected an atom", fn ->
278+
NIF.codec_multimap_atom_int64_alloc(keyword_with_invalid_key)
279+
end
280+
281+
assert_raise ArgumentError, "decode failed, expected an atom", fn ->
282+
NIF.codec_unordered_multimap_atom_int64(keyword_with_invalid_key)
283+
end
284+
285+
assert_raise ArgumentError, "decode failed, expected an atom", fn ->
286+
NIF.codec_unordered_multimap_atom_int64_alloc(keyword_with_invalid_key)
287+
end
288+
289+
keyword_with_invalid_value = [hello: 1.0]
290+
291+
assert_raise ArgumentError, "decode failed, expected an integer", fn ->
292+
NIF.codec_multimap_atom_int64(keyword_with_invalid_value)
293+
end
294+
295+
assert_raise ArgumentError, "decode failed, expected an integer", fn ->
296+
NIF.codec_multimap_atom_int64_alloc(keyword_with_invalid_value)
297+
end
298+
299+
assert_raise ArgumentError, "decode failed, expected an integer", fn ->
300+
NIF.codec_unordered_multimap_atom_int64(keyword_with_invalid_value)
301+
end
302+
303+
assert_raise ArgumentError, "decode failed, expected an integer", fn ->
304+
NIF.codec_unordered_multimap_atom_int64_alloc(keyword_with_invalid_value)
305+
end
306+
end
307+
236308
test "resource" do
237309
resource = NIF.resource_create(self())
238310
assert is_reference(resource)

0 commit comments

Comments
 (0)