Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
125 changes: 120 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,60 @@ 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;
}
};

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 +966,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 +1038,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 "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)
Expand Down