Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,8 @@ Fine provides implementations for the following types:
| `std::vector<T>` | x | x | `list(a)` |
| `std::map<K, V>` | x | x | `%{k => v}` |
| `std::unordered_map<K, V>` | x | x | `%{k => v}` |
| `std::multimap<K ,V>` | x | x | `list({k, v})` |
| `std::unordered_multimap<K ,V>` | x | x | `list({k, v})` |
| `fine::ResourcePtr<T>` | x | x | `reference` |
| `T` with [struct metadata](#structs) | x | x | `%a{}` |
| `fine::Ok<Args...>` | x | | `{:ok, ...}` |
Expand Down
133 changes: 128 additions & 5 deletions c_include/fine.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<T> implementation.
template <typename T> T decode(ErlNifEnv *env, const ERL_NIF_TERM &term) {
return Decoder<T>::decode(env, term);
return Decoder<std::remove_cv_t<T>>::decode(env, term);
}

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

// We want decode to return the value, and since the argument types
Expand Down Expand Up @@ -473,9 +473,10 @@ template <> struct Decoder<ErlNifPid> {
}
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;
}
Expand Down Expand Up @@ -591,6 +592,25 @@ template <typename... Args> struct Decoder<std::tuple<Args...>> {
}
};

template <typename T1, typename T2> struct Decoder<std::pair<T1, T2>> {
static std::pair<T1, T2> 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<T1>(env, terms[0]),
fine::decode<T2>(env, terms[1]));
}
};

template <typename T, typename Alloc> struct Decoder<std::vector<T, Alloc>> {
static std::vector<T, Alloc> decode(ErlNifEnv *env,
const ERL_NIF_TERM &term) {
Expand Down Expand Up @@ -690,6 +710,68 @@ struct Decoder<std::unordered_map<K, V, Hash, Pred, Alloc>> {
};
};

template <typename K, typename V, typename Compare, typename Alloc>
struct Decoder<std::multimap<K, V, Compare, Alloc>> {
static std::multimap<K, V, Compare, Alloc> 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<K, V, Compare, Alloc> map;

auto list = term;

ERL_NIF_TERM head, tail;
while (enif_get_list_cell(env, list, &head, &tail)) {
auto entry = fine::decode<std::pair<const K, V>>(env, head);

map.emplace(std::move(entry));

list = tail;
}

return map;
}
};

template <typename K, typename V, typename Hash, typename Pred, typename Alloc>
struct Decoder<std::unordered_multimap<K, V, Hash, Pred, Alloc>> {
static std::unordered_multimap<K, V, Hash, Pred, Alloc>
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<K, V, Hash, Pred, Alloc> map;

auto list = term;

ERL_NIF_TERM head, tail;
while (enif_get_list_cell(env, list, &head, &tail)) {
auto entry = fine::decode<std::pair<const K, V>>(env, head);

map.emplace(std::move(entry));

list = tail;
}

return map;
}

private:
struct IterCleanup {
ErlNifEnv *env;
ErlNifMapIterator iter;

~IterCleanup() { enif_map_iterator_destroy(env, &iter); }
};
Comment thread
brodeuralexis marked this conversation as resolved.
Outdated
};

template <typename T> struct Decoder<ResourcePtr<T>> {
static ResourcePtr<T> decode(ErlNifEnv *env, const ERL_NIF_TERM &term) {
void *ptr;
Expand Down Expand Up @@ -892,6 +974,14 @@ template <typename... Args> struct Encoder<std::tuple<Args...>> {
}
};

template <typename T1, typename T2> struct Encoder<std::pair<T1, T2>> {
static ERL_NIF_TERM encode(ErlNifEnv *env, const std::pair<T1, T2> &pair) {
const auto first = fine::encode<T1>(env, pair.first);
const auto second = fine::encode<T2>(env, pair.second);
return enif_make_tuple(env, 2, first, second);
}
};

template <typename T, typename Alloc> struct Encoder<std::vector<T, Alloc>> {
static ERL_NIF_TERM encode(ErlNifEnv *env,
const std::vector<T, Alloc> &vector) {
Expand Down Expand Up @@ -956,6 +1046,39 @@ struct Encoder<std::unordered_map<K, V, Hash, Pred, Alloc>> {
}
};

template <typename K, typename V, typename Compare, typename Alloc>
struct Encoder<std::multimap<K, V, Compare, Alloc>> {
static ERL_NIF_TERM encode(ErlNifEnv *env,
const std::multimap<K, V, Compare, Alloc> &map) {
auto terms = std::vector<ERL_NIF_TERM>();
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<unsigned int>(terms.size()));
}
};

template <typename K, typename V, typename Hash, typename Pred, typename Alloc>
struct Encoder<std::unordered_multimap<K, V, Hash, Pred, Alloc>> {
static ERL_NIF_TERM
encode(ErlNifEnv *env,
const std::unordered_multimap<K, V, Hash, Pred, Alloc> &map) {
auto terms = std::vector<ERL_NIF_TERM>();
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<unsigned int>(terms.size()));
}
};

template <typename T> struct Encoder<ResourcePtr<T>> {
static ERL_NIF_TERM encode(ErlNifEnv *env, const ResourcePtr<T> &resource) {
return enif_make_resource(env, reinterpret_cast<void *>(resource.get()));
Expand Down
41 changes: 41 additions & 0 deletions test/c_src/finest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,7 @@ codec_map_atom_int64(ErlNifEnv *, std::map<fine::Atom, int64_t> term) {
return term;
}
FINE_NIF(codec_map_atom_int64, 0);

std::map<fine::Atom, int64_t, std::less<fine::Atom>,
std::pmr::polymorphic_allocator<std::pair<const fine::Atom, int64_t>>>
codec_map_atom_int64_alloc(
Expand Down Expand Up @@ -237,6 +238,46 @@ codec_unordered_map_atom_int64_alloc(
}
FINE_NIF(codec_unordered_map_atom_int64_alloc, 0);

std::multimap<fine::Atom, int64_t>
codec_multimap_atom_int64(ErlNifEnv *,
std::multimap<fine::Atom, int64_t> term) {
return term;
}
FINE_NIF(codec_multimap_atom_int64, 0);

std::multimap<
fine::Atom, int64_t, std::less<fine::Atom>,
std::pmr::polymorphic_allocator<std::pair<const fine::Atom, int64_t>>>
codec_multimap_atom_int64_alloc(
ErlNifEnv *,
std::multimap<
fine::Atom, int64_t, std::less<fine::Atom>,
std::pmr::polymorphic_allocator<std::pair<const fine::Atom, int64_t>>>
term) {
return term;
}
FINE_NIF(codec_multimap_atom_int64_alloc, 0);

std::unordered_multimap<fine::Atom, int64_t>
codec_unordered_multimap_atom_int64(
ErlNifEnv *, std::unordered_multimap<fine::Atom, int64_t> term) {
return term;
}
FINE_NIF(codec_unordered_multimap_atom_int64, 0);

std::unordered_multimap<
fine::Atom, int64_t, std::hash<fine::Atom>, std::equal_to<fine::Atom>,
std::pmr::polymorphic_allocator<std::pair<const fine::Atom, int64_t>>>
codec_unordered_multimap_atom_int64_alloc(
ErlNifEnv *,
std::unordered_multimap<
fine::Atom, int64_t, std::hash<fine::Atom>, std::equal_to<fine::Atom>,
std::pmr::polymorphic_allocator<std::pair<const fine::Atom, int64_t>>>
term) {
return term;
}
FINE_NIF(codec_unordered_multimap_atom_int64_alloc, 0);

fine::ResourcePtr<TestResource>
codec_resource(ErlNifEnv *, fine::ResourcePtr<TestResource> term) {
return term;
Expand Down
4 changes: 4 additions & 0 deletions test/lib/finest/nif.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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!()
Expand Down
72 changes: 72 additions & 0 deletions test/test/finest_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,78 @@ defmodule FinestTest do
end
end

test "multimap" do
empty_multimap = []

small_multimap = [hello: 1, world: 2]

large_multimap =
0..64 |> Enum.map(fn x -> {:"a#{x}", x} end) |> Enum.to_list()
Comment thread
brodeuralexis marked this conversation as resolved.
Outdated

for multimap <- [empty_multimap, small_multimap, large_multimap] do
assert Enum.sort(NIF.codec_multimap_atom_int64(multimap)) == Enum.sort(multimap)
assert Enum.sort(NIF.codec_multimap_atom_int64_alloc(multimap)) == Enum.sort(multimap)
assert Enum.sort(NIF.codec_unordered_multimap_atom_int64(multimap)) == Enum.sort(multimap)

assert Enum.sort(NIF.codec_unordered_multimap_atom_int64_alloc(multimap)) ==
Enum.sort(multimap)
end

invalid_multimap = 10

assert_raise ArgumentError, "decode failed, expected a list", fn ->
NIF.codec_multimap_atom_int64(invalid_multimap)
end

assert_raise ArgumentError, "decode failed, expected a list", fn ->
NIF.codec_multimap_atom_int64_alloc(invalid_multimap)
end

assert_raise ArgumentError, "decode failed, expected a list", fn ->
NIF.codec_unordered_multimap_atom_int64(invalid_multimap)
end

assert_raise ArgumentError, "decode failed, expected a list", fn ->
NIF.codec_unordered_multimap_atom_int64_alloc(invalid_multimap)
end

multimap_with_invalid_key = [{"hello", 42}]

assert_raise ArgumentError, "decode failed, expected an atom", fn ->
NIF.codec_multimap_atom_int64(multimap_with_invalid_key)
end

assert_raise ArgumentError, "decode failed, expected an atom", fn ->
NIF.codec_multimap_atom_int64_alloc(multimap_with_invalid_key)
end

assert_raise ArgumentError, "decode failed, expected an atom", fn ->
NIF.codec_unordered_multimap_atom_int64(multimap_with_invalid_key)
end

assert_raise ArgumentError, "decode failed, expected an atom", fn ->
NIF.codec_unordered_multimap_atom_int64_alloc(multimap_with_invalid_key)
end

multimap_with_invalid_value = [hello: 1.0]

assert_raise ArgumentError, "decode failed, expected an integer", fn ->
NIF.codec_multimap_atom_int64(multimap_with_invalid_value)
end

assert_raise ArgumentError, "decode failed, expected an integer", fn ->
NIF.codec_multimap_atom_int64_alloc(multimap_with_invalid_value)
end

assert_raise ArgumentError, "decode failed, expected an integer", fn ->
NIF.codec_unordered_multimap_atom_int64(multimap_with_invalid_value)
end

assert_raise ArgumentError, "decode failed, expected an integer", fn ->
NIF.codec_unordered_multimap_atom_int64_alloc(multimap_with_invalid_value)
end
end

test "resource" do
resource = NIF.resource_create(self())
assert is_reference(resource)
Expand Down