Skip to content

Commit 297134b

Browse files
committed
Add more encoder and decoder implementations to fine
1 parent c7e18a5 commit 297134b

File tree

3 files changed

+261
-54
lines changed

3 files changed

+261
-54
lines changed

c_src/pythonx/fine.hpp

Lines changed: 194 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#include <cstdlib>
77
#include <cstring>
88
#include <erl_nif.h>
9+
#include <map>
910
#include <optional>
1011
#include <stdexcept>
1112
#include <string>
@@ -50,22 +51,28 @@ class Atom {
5051
inline static std::vector<Atom *> atoms = {};
5152
inline static bool initialized = false;
5253

53-
const char *name;
54+
std::string name;
5455
std::optional<ERL_NIF_UINT> term;
5556

5657
public:
57-
Atom(const char *name) : name(name), term(std::nullopt) {
58+
Atom(std::string name) : name(name), term(std::nullopt) {
5859
if (!Atom::initialized) {
5960
Atom::atoms.push_back(this);
6061
}
6162
}
6263

6364
std::string to_string() const { return this->name; }
6465

66+
bool operator==(const Atom &other) const { return this->name == other.name; }
67+
68+
bool operator==(const char *other) const { return this->name == other; }
69+
70+
bool operator<(const Atom &other) const { return this->name < other.name; }
71+
6572
private:
6673
static void init_atoms(ErlNifEnv *env) {
6774
for (auto atom : Atom::atoms) {
68-
atom->term = fine::__private__::make_atom(env, atom->name);
75+
atom->term = fine::__private__::make_atom(env, atom->name.c_str());
6976
}
7077

7178
Atom::atoms.clear();
@@ -205,6 +212,23 @@ template <> struct Decoder<double> {
205212
}
206213
};
207214

215+
template <> struct Decoder<bool> {
216+
static bool decode(ErlNifEnv *env, const ERL_NIF_TERM &term) {
217+
char atom_string[6];
218+
auto length = enif_get_atom(env, term, atom_string, 6, ERL_NIF_LATIN1);
219+
220+
if (length == 5 && strcmp(atom_string, "true") == 0) {
221+
return true;
222+
}
223+
224+
if (length == 6 && strcmp(atom_string, "false") == 0) {
225+
return false;
226+
}
227+
228+
throw std::invalid_argument("decode failed, expected a boolean");
229+
}
230+
};
231+
208232
template <> struct Decoder<ErlNifPid> {
209233
static ErlNifPid decode(ErlNifEnv *env, const ERL_NIF_TERM &term) {
210234
ErlNifPid pid;
@@ -233,6 +257,24 @@ template <> struct Decoder<std::string> {
233257
}
234258
};
235259

260+
template <> struct Decoder<Atom> {
261+
static Atom decode(ErlNifEnv *env, const ERL_NIF_TERM &term) {
262+
unsigned int length;
263+
if (!enif_get_atom_length(env, term, &length, ERL_NIF_LATIN1)) {
264+
throw std::invalid_argument("decode failed, expected an atom");
265+
}
266+
267+
auto buffer = std::make_unique<char[]>(length + 1);
268+
269+
// Note that enif_get_atom writes the NULL byte at the end
270+
if (!enif_get_atom(env, term, buffer.get(), length + 1, ERL_NIF_LATIN1)) {
271+
throw std::invalid_argument("decode failed, expected an atom");
272+
}
273+
274+
return Atom(std::string(buffer.get(), length));
275+
}
276+
};
277+
236278
template <typename T> struct Decoder<std::optional<T>> {
237279
static std::optional<T> decode(ErlNifEnv *env, const ERL_NIF_TERM &term) {
238280
char atom_string[4];
@@ -246,6 +288,29 @@ template <typename T> struct Decoder<std::optional<T>> {
246288
}
247289
};
248290

291+
template <typename... Args> struct Decoder<std::variant<Args...>> {
292+
static std::variant<Args...> decode(ErlNifEnv *env,
293+
const ERL_NIF_TERM &term) {
294+
return do_decode<Args...>(env, term);
295+
}
296+
297+
private:
298+
template <typename T, typename... Rest>
299+
static std::variant<Args...> do_decode(ErlNifEnv *env,
300+
const ERL_NIF_TERM &term) {
301+
try {
302+
return fine::decode<T>(env, term);
303+
} catch (std::invalid_argument) {
304+
if constexpr (sizeof...(Rest) > 0) {
305+
return do_decode<Rest...>(env, term);
306+
} else {
307+
throw std::invalid_argument(
308+
"decode failed, none of the variant types could be decoded");
309+
}
310+
}
311+
}
312+
};
313+
249314
template <typename... Args> struct Decoder<std::tuple<Args...>> {
250315
static std::tuple<Args...> decode(ErlNifEnv *env, const ERL_NIF_TERM &term) {
251316
constexpr auto expected_size = sizeof...(Args);
@@ -298,6 +363,37 @@ template <typename T> struct Decoder<std::vector<T>> {
298363
}
299364
};
300365

366+
template <typename K, typename V> struct Decoder<std::map<K, V>> {
367+
static std::map<K, V> decode(ErlNifEnv *env, const ERL_NIF_TERM &term) {
368+
auto map = std::map<K, V>();
369+
370+
ERL_NIF_TERM key, value;
371+
ErlNifMapIterator iter;
372+
if (!enif_map_iterator_create(env, term, &iter,
373+
ERL_NIF_MAP_ITERATOR_FIRST)) {
374+
throw std::invalid_argument("decode failed, expeted a map");
375+
}
376+
377+
// Define RAII cleanup for the iterator
378+
auto cleanup = IterCleanup{env, iter};
379+
380+
while (enif_map_iterator_get_pair(env, &iter, &key, &value)) {
381+
map[fine::decode<K>(env, key)] = fine::decode<V>(env, value);
382+
enif_map_iterator_next(env, &iter);
383+
}
384+
385+
return map;
386+
}
387+
388+
private:
389+
struct IterCleanup {
390+
ErlNifEnv *env;
391+
ErlNifMapIterator iter;
392+
393+
~IterCleanup() { enif_map_iterator_destroy(env, &iter); }
394+
};
395+
};
396+
301397
template <typename T> struct Decoder<ResourcePtr<T>> {
302398
static ResourcePtr<T> decode(ErlNifEnv *env, const ERL_NIF_TERM &term) {
303399
void *ptr;
@@ -397,9 +493,9 @@ template <> struct Encoder<ErlNifPid> {
397493
}
398494
};
399495

400-
template <> struct Encoder<std::nullopt_t> {
401-
static ERL_NIF_TERM encode(ErlNifEnv *env, const std::nullopt_t &nullopt) {
402-
return fine::encode(env, __private__::atoms::nil);
496+
template <> struct Encoder<ErlNifBinary> {
497+
static ERL_NIF_TERM encode(ErlNifEnv *env, const ErlNifBinary &binary) {
498+
return enif_make_binary(env, const_cast<ErlNifBinary *>(&binary));
403499
}
404500
};
405501

@@ -408,7 +504,7 @@ template <> struct Encoder<std::string> {
408504
ERL_NIF_TERM term;
409505
auto data = enif_make_new_binary(env, string.length(), &term);
410506
if (data == nullptr) {
411-
throw std::runtime_error("encode: failed to allocate new binary");
507+
throw std::runtime_error("encode failed, failed to allocate new binary");
412508
}
413509
memcpy(data, string.data(), string.length());
414510
return term;
@@ -420,11 +516,17 @@ template <> struct Encoder<Atom> {
420516
if (atom.term) {
421517
return atom.term.value();
422518
} else {
423-
return fine::__private__::make_atom(env, atom.name);
519+
return fine::__private__::make_atom(env, atom.name.c_str());
424520
}
425521
}
426522
};
427523

524+
template <> struct Encoder<std::nullopt_t> {
525+
static ERL_NIF_TERM encode(ErlNifEnv *env, const std::nullopt_t &nullopt) {
526+
return fine::encode(env, __private__::atoms::nil);
527+
}
528+
};
529+
428530
template <typename T> struct Encoder<std::optional<T>> {
429531
static ERL_NIF_TERM encode(ErlNifEnv *env, const std::optional<T> &optional) {
430532
if (optional) {
@@ -435,6 +537,28 @@ template <typename T> struct Encoder<std::optional<T>> {
435537
}
436538
};
437539

540+
template <typename... Args> struct Encoder<std::variant<Args...>> {
541+
static ERL_NIF_TERM encode(ErlNifEnv *env,
542+
const std::variant<Args...> &variant) {
543+
return do_encode<Args...>(env, variant);
544+
}
545+
546+
private:
547+
template <typename T, typename... Rest>
548+
static ERL_NIF_TERM do_encode(ErlNifEnv *env,
549+
const std::variant<Args...> &variant) {
550+
if (auto value = std::get_if<T>(&variant)) {
551+
return fine::encode(env, *value);
552+
}
553+
554+
if constexpr (sizeof...(Rest) > 0) {
555+
return do_encode<Rest...>(env, variant);
556+
} else {
557+
throw std::runtime_error("unreachable");
558+
}
559+
}
560+
};
561+
438562
template <typename... Args> struct Encoder<std::tuple<Args...>> {
439563
static ERL_NIF_TERM encode(ErlNifEnv *env, const std::tuple<Args...> &tuple) {
440564
return do_encode(env, tuple, std::make_index_sequence<sizeof...(Args)>());
@@ -451,49 +575,37 @@ template <typename... Args> struct Encoder<std::tuple<Args...>> {
451575
}
452576
};
453577

454-
template <typename... Args> struct Encoder<Ok<Args...>> {
455-
static ERL_NIF_TERM encode(ErlNifEnv *env, const Ok<Args...> &ok) {
456-
auto tag = __private__::atoms::ok;
578+
template <typename T> struct Encoder<std::vector<T>> {
579+
static ERL_NIF_TERM encode(ErlNifEnv *env, const std::vector<T> &vector) {
580+
auto terms = std::vector<ERL_NIF_TERM>();
581+
terms.reserve(vector.size());
457582

458-
if constexpr (sizeof...(Args) > 0) {
459-
return fine::encode(env, std::tuple_cat(std::tuple(tag), ok.items));
460-
} else {
461-
return fine::encode(env, tag);
583+
for (const auto &item : vector) {
584+
terms.push_back(fine::encode(env, item));
462585
}
463-
}
464-
};
465586

466-
template <typename... Args> struct Encoder<Error<Args...>> {
467-
static ERL_NIF_TERM encode(ErlNifEnv *env, const Error<Args...> &error) {
468-
auto tag = __private__::atoms::error;
469-
470-
if constexpr (sizeof...(Args) > 0) {
471-
return fine::encode(env, std::tuple_cat(std::tuple(tag), error.items));
472-
} else {
473-
return fine::encode(env, tag);
474-
}
587+
return enif_make_list_from_array(env, terms.data(),
588+
static_cast<unsigned int>(terms.size()));
475589
}
476590
};
477591

478-
template <typename... Args> struct Encoder<std::variant<Args...>> {
479-
static ERL_NIF_TERM encode(ErlNifEnv *env,
480-
const std::variant<Args...> &variant) {
481-
return do_encode<Args...>(env, variant);
482-
}
592+
template <typename K, typename V> struct Encoder<std::map<K, V>> {
593+
static ERL_NIF_TERM encode(ErlNifEnv *env, const std::map<K, V> &map) {
594+
auto keys = std::vector<ERL_NIF_TERM>();
595+
auto values = std::vector<ERL_NIF_TERM>();
483596

484-
private:
485-
template <typename T, typename... Rest>
486-
static ERL_NIF_TERM do_encode(ErlNifEnv *env,
487-
const std::variant<Args...> &variant) {
488-
if (auto value = std::get_if<T>(&variant)) {
489-
return fine::encode(env, *value);
597+
for (const auto &[key, value] : map) {
598+
keys.push_back(fine::encode(env, key));
599+
values.push_back(fine::encode(env, value));
490600
}
491601

492-
if constexpr (sizeof...(Rest) > 0) {
493-
return do_encode<Rest...>(env, variant);
494-
} else {
495-
throw std::runtime_error("unreachable");
602+
ERL_NIF_TERM map_term;
603+
if (!enif_make_map_from_arrays(env, keys.data(), values.data(), keys.size(),
604+
&map_term)) {
605+
throw std::runtime_error("encode failed, failed to make a map");
496606
}
607+
608+
return map_term;
497609
}
498610
};
499611

@@ -531,7 +643,7 @@ struct Encoder<T, std::void_t<decltype(T::module), decltype(T::fields)>> {
531643
ERL_NIF_TERM map;
532644
if (!enif_make_map_from_arrays(env, keys, values,
533645
num_extra_fields + num_fields, &map)) {
534-
throw std::runtime_error("encode: failed to make a map");
646+
throw std::runtime_error("encode failed, failed to make a map");
535647
}
536648

537649
return map;
@@ -570,6 +682,30 @@ struct Encoder<T, std::void_t<decltype(T::module), decltype(T::fields)>> {
570682
: std::true_type {};
571683
};
572684

685+
template <typename... Args> struct Encoder<Ok<Args...>> {
686+
static ERL_NIF_TERM encode(ErlNifEnv *env, const Ok<Args...> &ok) {
687+
auto tag = __private__::atoms::ok;
688+
689+
if constexpr (sizeof...(Args) > 0) {
690+
return fine::encode(env, std::tuple_cat(std::tuple(tag), ok.items));
691+
} else {
692+
return fine::encode(env, tag);
693+
}
694+
}
695+
};
696+
697+
template <typename... Args> struct Encoder<Error<Args...>> {
698+
static ERL_NIF_TERM encode(ErlNifEnv *env, const Error<Args...> &error) {
699+
auto tag = __private__::atoms::error;
700+
701+
if constexpr (sizeof...(Args) > 0) {
702+
return fine::encode(env, std::tuple_cat(std::tuple(tag), error.items));
703+
} else {
704+
return fine::encode(env, tag);
705+
}
706+
}
707+
};
708+
573709
namespace __private__ {
574710
class ExceptionError : public std::exception {
575711
public:
@@ -711,6 +847,13 @@ ResourcePtr<T> make_resource(Args &&...args) {
711847
return resource;
712848
}
713849

850+
template <typename T>
851+
Term make_resource_binary(ErlNifEnv *env, ResourcePtr<T> resource,
852+
const char *data, size_t size) {
853+
return enif_make_resource_binary(
854+
env, reinterpret_cast<void *>(resource.get()), data, size);
855+
}
856+
714857
class Registration {
715858
inline static std::vector<std::tuple<ErlNifResourceType **, const char *,
716859
void (*)(ErlNifEnv *, void *)>>
@@ -849,11 +992,19 @@ inline int load(ErlNifEnv *env, void **priv_data, ERL_NIF_TERM load_info) {
849992
{#name, fine::nif_arity(name), name##_nif, flags}); \
850993
static_assert(true, "require a semicolon after the macro")
851994

995+
// Note that we use static, in case FINE_REASOURCE is used in another
996+
// translation unit on the same line
997+
852998
#define FINE_RESOURCE(class_name) \
853-
auto __resource_registration_##class_name = \
999+
static auto __FINE_CONCAT__(__resource_registration_, __LINE__) = \
8541000
fine::Registration::register_resource<class_name>(#class_name); \
8551001
static_assert(true, "require a semicolon after the macro")
8561002

1003+
// An extra level of indirection is necessary to make sure __LINE__
1004+
// is expanded before concatenation
1005+
#define __FINE_CONCAT__(a, b) __FINE_CONCAT_IMPL__(a, b)
1006+
#define __FINE_CONCAT_IMPL__(a, b) a##b
1007+
8571008
// This is a modified version of ERL_NIF_INIT that points to the
8581009
// registered NIF functions and also sets the load callback.
8591010
#define FINE_INIT(name) \

0 commit comments

Comments
 (0)