From 261e477a8df2fd9be2edab632b8022717a264eb5 Mon Sep 17 00:00:00 2001 From: vector-of-bool Date: Wed, 18 Dec 2024 12:25:00 -0700 Subject: [PATCH 01/14] Tweak style of code blocks --- docs/_static/styles.css | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/docs/_static/styles.css b/docs/_static/styles.css index c7e937a..6e6bd01 100644 --- a/docs/_static/styles.css +++ b/docs/_static/styles.css @@ -31,4 +31,18 @@ dl[class].cpp { a.reference.external::after { content: '⭧'; font-size: 80%; +} + +.highlight { + + /* Separate line numbers visually */ + .linenos { + border-right: 1px solid var(--pst-color-border); + margin-right: 10px; + } + + /* Italicize comments */ + .c1 { + font-style: italic; + } } \ No newline at end of file From a0cf8079c57e0c5a4d1b1209fb46df66d597bd28 Mon Sep 17 00:00:00 2001 From: vector-of-bool Date: Wed, 18 Dec 2024 12:27:01 -0700 Subject: [PATCH 02/14] Add convenience overload of then/let --- docs/ref/async.rst | 54 ++++++++++-- include/amongoc/async.h | 174 ++++++++++++++++++++++++++++++++++++- src/amongoc/async.let.cpp | 10 +-- src/amongoc/async.then.cpp | 10 +-- 4 files changed, 230 insertions(+), 18 deletions(-) diff --git a/docs/ref/async.rst b/docs/ref/async.rst index fe96b49..5b711d9 100644 --- a/docs/ref/async.rst +++ b/docs/ref/async.rst @@ -14,9 +14,23 @@ Asynchronous Utility APIs Functions ######### +Synchronous Continuations +************************* + .. function:: - amongoc_emitter [[type(U)]] amongoc_then(\ - amongoc_emitter [[transfer, type(T)]] em, \ + [[1]] amongoc_emitter [[type(U)]] \ + amongoc_then(amongoc_emitter [[transfer, type(T)]] in, amongoc_then_transformer [[type(In=T, Out=U)]] tr) + [[2]] amongoc_emitter [[type(U)]] \ + amongoc_then(amongoc_emitter [[transfer, type(T)]] in, amongoc_box [[transfer, type(User)]] userdata, amongoc_then_transformer [[type(In=T, Out=U, User=User)]] tr) + [[3]] amongoc_emitter [[type(U)]] \ + amongoc_then(amongoc_emitter [[transfer, type(T)]] in, amongoc_async_flags flags, amongoc_then_transformer [[type(In=T, Out=U)]] tr) + [[4]] amongoc_emitter [[type(U)]] \ + amongoc_then(amongoc_emitter [[transfer, type(T)]] in, amongoc_async_flags flags, amongoc_box [[transfer, type(User)]] userdata, amongoc_then_transformer [[type(In=T, Out=U, User=User)]] tr) + [[5]] amongoc_emitter [[type(U)]] \ + amongoc_then(amongoc_emitter [[transfer, type(T)]] in, mlib_allocator alloc, amongoc_box [[transfer, type(User)]] userdata, amongoc_then_transformer [[type(In=T, Out=U, User=User)]] tr) + [[6]] amongoc_emitter [[type(U)]] \ + amongoc_then( \ + amongoc_emitter [[transfer, type(T)]] in, \ amongoc_async_flags flags, \ mlib_allocator alloc, \ amongoc_box [[transfer, type(UserData)]] userdata, \ @@ -24,7 +38,7 @@ Functions Connect a continuation to an `amongoc_emitter`. - :param em: |attr.transfer| The emitter to be composed. + :param in: |attr.transfer| The emitter to be composed. :param flags: Options for the behavior of the transformed emitter. :param alloc: |opstate-alloc| :param userdata: |attr.transfer| Arbitrary userdata that will be forwarded to `tr`. @@ -32,7 +46,7 @@ Functions :return: A new `amongoc_emitter` |R| :header: |this-header| - When the emitter `em` resolves, its result status and value will be given to + When the emitter `in` resolves, its result status and value will be given to `tr`. The return value from `tr` will become the new result value of the returned emitter |R|. The transform function `tr` may also modify the final status to change the result status of |R|. @@ -43,10 +57,35 @@ Functions result, use `amongoc_let` instead. `amongoc_then` is only for synchronous continuations. + Overloads: + + 1. Equivalent to :expr:`amongoc_then(in, amongoc_async_default, mlib_default_allocator, amongoc_nil, tr)` + 2. Equivalent to :expr:`amongoc_then(in, amongoc_async_default, mlib_default_allocator, userdata, tr)` + 3. Equivalent to :expr:`amongoc_then(in, flags, mlib_default_allocator, amongoc_nil, tr)` + 4. Equivalent to :expr:`amongoc_then(in, flags, mlib_default_allocator, userdata, tr)` + 5. Equivalent to :expr:`amongoc_then(in, amongoc_async_default, alloc, userdata, tr)` + 6. Specifies all five parameters + + .. note:: |macro-impl|. + + +Asynchronous Continuations +************************** .. function:: - amongoc_emitter [[type(U)]] amongoc_let(\ - amongoc_emitter [[transfer, type(T)]] em, \ + [[1]] amongoc_emitter [[type(U)]] \ + amongoc_let(amongoc_emitter [[transfer, type(T)]] in, amongoc_let_transformer [[type(In=T, Out=U)]] tr) + [[2]] amongoc_emitter [[type(U)]] \ + amongoc_let(amongoc_emitter [[transfer, type(T)]] in, amongoc_box [[transfer, type(User)]] userdata, amongoc_let_transformer [[type(In=T, Out=U, User=User)]] tr) + [[3]] amongoc_emitter [[type(U)]] \ + amongoc_let(amongoc_emitter [[transfer, type(T)]] in, amongoc_async_flags flags, amongoc_let_transformer [[type(In=T, Out=U)]] tr) + [[4]] amongoc_emitter [[type(U)]] \ + amongoc_let(amongoc_emitter [[transfer, type(T)]] in, amongoc_async_flags flags, amongoc_box [[transfer, type(User)]] userdata, amongoc_let_transformer [[type(In=T, Out=U, User=User)]] tr) + [[5]] amongoc_emitter [[type(U)]] \ + amongoc_let(amongoc_emitter [[transfer, type(T)]] in, mlib_allocator alloc, amongoc_box [[transfer, type(User)]] userdata, amongoc_let_transformer [[type(In=T, Out=U, User=User)]] tr) + [[6]] amongoc_emitter [[type(U)]] \ + amongoc_let( \ + amongoc_emitter [[transfer, type(T)]] in, \ amongoc_async_flags flags, \ mlib_allocator alloc, \ amongoc_box [[transfer, type(UserData)]] userdata, \ @@ -72,6 +111,9 @@ Functions the result of another asynchronous operation. +Other +***** + .. function:: amongoc_emitter [[type(T)]] amongoc_just(amongoc_status st, amongoc_box [[transfer, type(T)]] value, mlib_allocator alloc) Create an emitter that will resolve immediately with the given status and diff --git a/include/amongoc/async.h b/include/amongoc/async.h index d9f94b0..74e8a5b 100644 --- a/include/amongoc/async.h +++ b/include/amongoc/async.h @@ -31,7 +31,7 @@ mlib_extern_c_begin(); */ typedef amongoc_box (*amongoc_then_transformer)(amongoc_box userdata, amongoc_status* status, - amongoc_box value) mlib_noexcept; + amongoc_box value); /** * @brief Function type for the `amongoc_let` transformation callback @@ -45,7 +45,7 @@ typedef amongoc_box (*amongoc_then_transformer)(amongoc_box userdata, */ typedef amongoc_emitter (*amongoc_let_transformer)(amongoc_box userdata, amongoc_status status, - amongoc_box value) mlib_noexcept; + amongoc_box value); /// Flags to control the behavior of asynchronous utilities enum amongoc_async_flags { @@ -82,6 +82,91 @@ amongoc_emitter amongoc_then(amongoc_emitter em, amongoc_box userdata, amongoc_then_transformer tr) mlib_noexcept; +#if mlib_is_cxx() +extern "C++" { +inline amongoc_emitter _amongoc_then_cxx(amongoc_emitter em, + amongoc_then_transformer tr) mlib_noexcept { + return ::amongoc_then(em, amongoc_async_default, mlib_default_allocator, amongoc_nil, tr); +} +inline amongoc_emitter _amongoc_then_cxx(amongoc_emitter em, + amongoc_box userdata, + amongoc_then_transformer tr) mlib_noexcept { + return ::amongoc_then(em, amongoc_async_default, mlib_default_allocator, userdata, tr); +} +inline amongoc_emitter _amongoc_then_cxx(amongoc_emitter em, + amongoc_async_flags flags, + amongoc_then_transformer tr) mlib_noexcept { + return ::amongoc_then(em, flags, mlib_default_allocator, amongoc_nil, tr); +} +inline amongoc_emitter _amongoc_then_cxx(amongoc_emitter em, + mlib_allocator alloc, + amongoc_box userdata, + amongoc_then_transformer tr) mlib_noexcept { + return ::amongoc_then(em, amongoc_async_default, alloc, userdata, tr); +} +inline amongoc_emitter _amongoc_then_cxx(amongoc_emitter em, + amongoc_async_flags flags, + amongoc_box userdata, + amongoc_then_transformer tr) mlib_noexcept { + return ::amongoc_then(em, flags, mlib_default_allocator, userdata, tr); +} +inline amongoc_emitter _amongoc_then_cxx(amongoc_emitter em, + amongoc_async_flags flags, + mlib_allocator alloc, + amongoc_box userdata, + amongoc_then_transformer tr) mlib_noexcept { + return ::amongoc_then(em, flags, alloc, userdata, tr); +} +} +#define amongoc_then(...) _amongoc_then_cxx(__VA_ARGS__) +#else +#define amongoc_then(...) MLIB_PASTE(_amongocThenArgc_, MLIB_ARG_COUNT(__VA_ARGS__))(__VA_ARGS__) +// clang-format off +// 2-args: An emitter and continuation +#define _amongocThenArgc_2(Emitter, Then) \ + amongoc_then((Emitter), amongoc_async_default, mlib_default_allocator, amongoc_nil, (Then)) +// 3-args: One of: +// - then(em, userdata, cb) +// - then(em, flags, cb) +#define _amongocThenArgc_3(Emitter, UdOrFlags, Then) \ + mlib_generic(_amongoc_then_cxx, _amongoc_then_em_ud_cb, (UdOrFlags), \ + amongoc_box: _amongoc_then_em_ud_cb, \ + enum amongoc_async_flags: _amongoc_then_em_fl_cb \ + )(Emitter, UdOrFlags, Then) +// 4-args: One of: +// - then(em, alloc, userdata, cb) +// - then(em, flags, userdata, cb) +#define _amongocThenArgc_4(Emitter, AllocOrFlags, Userdata, Then) \ + mlib_generic(_amongoc_then_cxx, _amongoc_then_em_al_ud_cb, (AllocOrFlags), \ + mlib_allocator: _amongoc_then_em_al_ud_cb, \ + enum amongoc_async_flags: _amongoc_then_em_fl_ud_cb\ + )((Emitter, AllocOrFlags, Userdata, Then)) +#define _amongocThenArgc_5 amongoc_then +// clang-format on +static inline amongoc_emitter _amongoc_then_em_ud_cb(amongoc_emitter em, + amongoc_box userdata, + amongoc_then_transformer tr) mlib_noexcept { + return amongoc_then(em, amongoc_async_default, mlib_default_allocator, userdata, tr); +} +static inline amongoc_emitter _amongoc_then_em_fl_cb(amongoc_emitter em, + enum amongoc_async_flags fl, + amongoc_then_transformer tr) mlib_noexcept { + return amongoc_then(em, fl, mlib_default_allocator, amongoc_nil, tr); +} +static inline amongoc_emitter _amongoc_then_em_al_ud_cb(amongoc_emitter em, + mlib_allocator alloc, + amongoc_box userdata, + amongoc_then_transformer tr) mlib_noexcept { + return amongoc_then(em, amongoc_async_default, alloc, userdata, tr); +} +static inline amongoc_emitter _amongoc_then_em_fl_ud_cb(amongoc_emitter em, + enum amongoc_async_flags flags, + amongoc_box userdata, + amongoc_then_transformer tr) mlib_noexcept { + return amongoc_then(em, flags, mlib_default_allocator, userdata, tr); +} +#endif + /** * @brief Transform the result of an asynchronous operation and continue to a * new asynchronous operation. @@ -101,6 +186,91 @@ amongoc_emitter amongoc_let(amongoc_emitter em, amongoc_box userdata, amongoc_let_transformer tr) mlib_noexcept; +#if mlib_is_cxx() +extern "C++" { +inline amongoc_emitter _amongoc_let_cxx(amongoc_emitter em, + amongoc_let_transformer tr) mlib_noexcept { + return ::amongoc_let(em, amongoc_async_default, mlib_default_allocator, amongoc_nil, tr); +} +inline amongoc_emitter _amongoc_let_cxx(amongoc_emitter em, + amongoc_box userdata, + amongoc_let_transformer tr) mlib_noexcept { + return ::amongoc_let(em, amongoc_async_default, mlib_default_allocator, userdata, tr); +} +inline amongoc_emitter _amongoc_let_cxx(amongoc_emitter em, + amongoc_async_flags flags, + amongoc_let_transformer tr) mlib_noexcept { + return ::amongoc_let(em, flags, mlib_default_allocator, amongoc_nil, tr); +} +inline amongoc_emitter _amongoc_let_cxx(amongoc_emitter em, + mlib_allocator alloc, + amongoc_box userdata, + amongoc_let_transformer tr) mlib_noexcept { + return ::amongoc_let(em, amongoc_async_default, alloc, userdata, tr); +} +inline amongoc_emitter _amongoc_let_cxx(amongoc_emitter em, + amongoc_async_flags flags, + amongoc_box userdata, + amongoc_let_transformer tr) mlib_noexcept { + return ::amongoc_let(em, flags, mlib_default_allocator, userdata, tr); +} +inline amongoc_emitter _amongoc_let_cxx(amongoc_emitter em, + amongoc_async_flags flags, + mlib_allocator alloc, + amongoc_box userdata, + amongoc_let_transformer tr) mlib_noexcept { + return ::amongoc_let(em, flags, alloc, userdata, tr); +} +} +#define amongoc_let(...) _amongoc_let_cxx(__VA_ARGS__) +#else +#define amongoc_let(...) MLIB_PASTE(_amongocLetArgc_, MLIB_ARG_COUNT(__VA_ARGS__))(__VA_ARGS__) +// clang-format off +// 2-args: An emitter and continuation +#define _amongocLetArgc_2(Emitter, Then) \ + amongoc_let((Emitter), amongoc_async_default, mlib_default_allocator, amongoc_nil, (Then)) +// 3-args: One of: +// - then(em, userdata, cb) +// - then(em, flags, cb) +#define _amongocLetArgc_3(Emitter, UdOrFlags, Then) \ + mlib_generic(_amongoc_let_cxx, _amongoc_let_em_ud_cb, (UdOrFlags), \ + amongoc_box: _amongoc_let_em_ud_cb, \ + enum amongoc_async_flags: _amongoc_let_em_fl_cb \ + )(Emitter, UdOrFlags, Then) +// 4-args: One of: +// - then(em, alloc, userdata, cb) +// - then(em, flags, userdata, cb) +#define _amongocLetArgc_4(Emitter, AllocOrFlags, Userdata, Then) \ + mlib_generic(_amongoc_let_cxx, _amongoc_let_em_al_ud_cb, (AllocOrFlags), \ + mlib_allocator: _amongoc_let_em_al_ud_cb, \ + enum amongoc_async_flags: _amongoc_let_em_fl_ud_cb\ + )((Emitter, AllocOrFlags, Userdata, Then)) +#define _amongocLetArgc_5 amongoc_let +// clang-format on +static inline amongoc_emitter _amongoc_let_em_ud_cb(amongoc_emitter em, + amongoc_box userdata, + amongoc_let_transformer tr) mlib_noexcept { + return amongoc_let(em, amongoc_async_default, mlib_default_allocator, userdata, tr); +} +static inline amongoc_emitter _amongoc_let_em_fl_cb(amongoc_emitter em, + enum amongoc_async_flags fl, + amongoc_let_transformer tr) mlib_noexcept { + return amongoc_let(em, fl, mlib_default_allocator, amongoc_nil, tr); +} +static inline amongoc_emitter _amongoc_let_em_al_ud_cb(amongoc_emitter em, + mlib_allocator alloc, + amongoc_box userdata, + amongoc_let_transformer tr) mlib_noexcept { + return amongoc_let(em, amongoc_async_default, alloc, userdata, tr); +} +static inline amongoc_emitter _amongoc_let_em_fl_ud_cb(amongoc_emitter em, + enum amongoc_async_flags flags, + amongoc_box userdata, + amongoc_let_transformer tr) mlib_noexcept { + return amongoc_let(em, flags, mlib_default_allocator, userdata, tr); +} +#endif + /** * @brief Create an emitter that resolves immediately with the given status and value * diff --git a/src/amongoc/async.let.cpp b/src/amongoc/async.let.cpp index 90ab789..25fbfe7 100644 --- a/src/amongoc/async.let.cpp +++ b/src/amongoc/async.let.cpp @@ -89,11 +89,11 @@ static unique_emitter _let(CompressedEmitter&& in, return as_emitter(mlib::allocator<>{get_alloc()}, mlib_fwd(l)); } -emitter amongoc_let(emitter in_, - amongoc_async_flags flags, - mlib_allocator alloc, - box userdata_, - amongoc_let_transformer tr) noexcept { +emitter(amongoc_let)(emitter in_, + amongoc_async_flags flags, + mlib_allocator alloc, + box userdata_, + amongoc_let_transformer tr) noexcept { auto ud = mlib_fwd(userdata_).as_unique(); auto in = mlib_fwd(in_).as_unique(); auto let_1 = [&](auto get_alloc) -> unique_emitter { diff --git a/src/amongoc/async.then.cpp b/src/amongoc/async.then.cpp index b658a41..1de90c2 100644 --- a/src/amongoc/async.then.cpp +++ b/src/amongoc/async.then.cpp @@ -38,11 +38,11 @@ static unique_emitter _then(CompressedEmitter&& em, ud)})); } -emitter amongoc_then(emitter in, - amongoc_async_flags flags, - mlib_allocator alloc_, - box userdata_, - amongoc_then_transformer tr) noexcept { +emitter(amongoc_then)(emitter in, + amongoc_async_flags flags, + mlib_allocator alloc_, + box userdata_, + amongoc_then_transformer tr) noexcept { auto alloc = mlib::allocator<>{alloc_}; return mlib_fwd(in) .as_unique() From 10665f7d8399d9450e92611aa9d85f3fad3b6aa0 Mon Sep 17 00:00:00 2001 From: vector-of-bool Date: Wed, 18 Dec 2024 14:08:52 -0700 Subject: [PATCH 03/14] Fix more generic signatures for BSON views and values --- include/bson/doc.h | 27 +++++++++++++++++---------- include/bson/value.h | 5 +++++ include/bson/value_ref.h | 1 + src/bson/doc.c | 26 +++++++++++++++----------- 4 files changed, 38 insertions(+), 21 deletions(-) diff --git a/include/bson/doc.h b/include/bson/doc.h index bc29c75..374c59c 100644 --- a/include/bson/doc.h +++ b/include/bson/doc.h @@ -230,12 +230,14 @@ typedef struct bson_mut { mlib_allocator: _bson_new_with_alloc, \ bson_doc: _bson_copy_doc, \ bson_view: _bson_copy_view_with_default_allocator, \ + bson_array_view: _bson_copy_array_view_with_default_allocator, \ struct bson_mut: _bson_copy_mut_with_default_allocator, \ default: _bson_new_reserve_with_default_alloc)(X) #define _bsonNewArgc_2(ReserveOrDoc, Alloc) \ _Generic((ReserveOrDoc), \ bson_doc: _bson_copy_doc_with_allocator, \ bson_view: _bson_copy_view_with_allocator, \ + bson_array_view: _bson_copy_array_view_with_allocator, \ struct bson_mut: _bson_copy_mut_with_allocator, \ default: _bson_new)((ReserveOrDoc), (Alloc)) @@ -281,16 +283,12 @@ inline bson_doc _bson_copy_view_with_allocator(bson_view other, } return ret; } -inline bson_doc _bson_copy_mut_with_allocator(struct bson_mut other, - mlib_allocator alloc) mlib_noexcept { - bson_doc ret = _bson_new(bson_size(other), alloc); - // If we copied an empty doc, then we did not allocate memory, and we point to - // the global empty instance. We should not attempt to write anything there. - // It is already statically initialized. - if (ret._bson_document_data != _bson_get_global_empty_doc_data()) { - memcpy(bson_mut_data(ret), bson_data(other), bson_size(other)); - } - return ret; +inline bson_doc _bson_copy_array_view_with_allocator(bson_array_view other, + mlib_allocator alloc) mlib_noexcept { + return _bson_copy_view_with_allocator(bson_view_from(other), alloc); +} +inline bson_doc _bson_copy_mut_with_allocator(bson_mut other, mlib_allocator alloc) mlib_noexcept { + return _bson_copy_view_with_allocator(bson_view_from(other), alloc); } inline bson_doc _bson_copy_doc_with_allocator(bson_doc other, mlib_allocator alloc) mlib_noexcept { return _bson_copy_view_with_allocator(bson_view_from(other), alloc); @@ -304,6 +302,9 @@ inline bson_doc _bson_new_reserve_with_default_alloc(uint32_t n) mlib_noexcept { inline bson_doc _bson_new_with_alloc(mlib_allocator a) mlib_noexcept { return _bson_new(5, a); } // [[6]] +inline bson_doc _bson_copy_array_view_with_default_allocator(bson_array_view view) mlib_noexcept { + return _bson_copy_view_with_allocator(bson_view_from(view), mlib_default_allocator); +} inline bson_doc _bson_copy_view_with_default_allocator(bson_view view) mlib_noexcept { return _bson_copy_view_with_allocator(view, mlib_default_allocator); } @@ -328,6 +329,9 @@ inline bson_doc _bson_new_generic(std::uint32_t reserve, ::mlib_allocator alloc) inline bson_doc _bson_new_generic(bson_view doc, ::mlib_allocator alloc) noexcept { return ::_bson_copy_view_with_allocator(doc, alloc); } +inline bson_doc _bson_new_generic(bson_array_view doc, ::mlib_allocator alloc) noexcept { + return ::_bson_copy_view_with_allocator(bson_view(doc), alloc); +} inline bson_doc _bson_new_generic(bson_doc doc, ::mlib_allocator alloc) noexcept { return ::_bson_copy_view_with_allocator(bson_view_from(doc), alloc); } @@ -346,6 +350,9 @@ inline bson_doc _bson_new_generic(bson_doc doc) noexcept { inline bson_doc _bson_new_generic(bson_view doc) noexcept { return ::bson_new(bson_view_from(doc), ::mlib_default_allocator); } +inline bson_doc _bson_new_generic(bson_array_view doc) noexcept { + return ::bson_new(bson_view_from(doc), ::mlib_default_allocator); +} inline bson_doc _bson_new_generic(bson_mut doc) noexcept { return ::bson_new(bson_view_from(doc), ::mlib_default_allocator); } diff --git a/include/bson/value.h b/include/bson/value.h index 34c8dc7..95dffd1 100644 --- a/include/bson/value.h +++ b/include/bson/value.h @@ -235,10 +235,15 @@ mlib_constexpr bson_value_ref _bson_value_ref_from_value(bson_value val) mlib_no bson_dbpointer_view: _bson_value_ref_from_bson_dbpointer_view, \ bson_code_view: _bson_value_ref_from_bson_code_view, \ bson_symbol_view: _bson_value_ref_from_bson_symbol_view, \ + int8_t: _bson_value_ref_from_int32_t, \ + uint8_t: _bson_value_ref_from_int32_t, \ + int16_t: _bson_value_ref_from_int32_t, \ + uint16_t: _bson_value_ref_from_int32_t, \ int32_t: _bson_value_ref_from_int32_t, \ bson_timestamp: _bson_value_ref_from_bson_timestamp, \ bson_decimal128: _bson_value_ref_from_bson_decimal128, \ int64_t: _bson_value_ref_from_int64_t, \ + uint32_t: _bson_value_ref_from_int64_t, \ bson_value: _bson_value_ref_from_value, \ bson_value_ref: _bson_value_ref_dup)((X)) diff --git a/include/bson/value_ref.h b/include/bson/value_ref.h index 4c6ce94..11e9942 100644 --- a/include/bson/value_ref.h +++ b/include/bson/value_ref.h @@ -91,6 +91,7 @@ typedef struct bson_value_ref { DECL_CONVERSION(bson_symbol_view, ::bson_type_symbol, symbol, arg); DECL_CONVERSION(std::int32_t, ::bson_type_int32, int32, arg); DECL_CONVERSION(bson_timestamp, ::bson_type_timestamp, timestamp, arg); + DECL_CONVERSION(std::uint32_t, ::bson_type_int64, int64, arg); DECL_CONVERSION(std::int64_t, ::bson_type_int64, int64, arg); DECL_CONVERSION(bson_decimal128, ::bson_type_decimal128, decimal128, arg); DECL_CONVERSION(bson::null, ::bson_type_null, int32, 0); diff --git a/src/bson/doc.c b/src/bson/doc.c index 5f3334e..8babcf1 100644 --- a/src/bson/doc.c +++ b/src/bson/doc.c @@ -5,23 +5,27 @@ #include -extern inline uint32_t bson_doc_capacity(bson_doc d) mlib_noexcept; +extern mlib_constexpr uint32_t bson_doc_capacity(bson_doc d) mlib_noexcept; extern mlib_constexpr mlib_allocator bson_doc_get_allocator(bson_doc m) mlib_noexcept; extern inline bool _bson_realloc(bson_doc* doc, uint32_t new_size) mlib_noexcept; -extern mlib_constexpr int32_t bson_doc_reserve(bson_doc* d, uint32_t size) mlib_noexcept; +extern inline int32_t bson_doc_reserve(bson_doc* d, uint32_t size) mlib_noexcept; extern inline bson_doc _bson_new(uint32_t reserve, mlib_allocator allocator) mlib_noexcept; extern inline bson_doc _bson_copy_doc(bson_doc) mlib_noexcept; extern inline bson_doc _bson_new_with_alloc(mlib_allocator a) mlib_noexcept; extern inline bson_doc _bson_copy_view_with_default_allocator(bson_view view) mlib_noexcept; -extern inline bson_doc _bson_copy_mut_with_default_allocator(bson_mut m) mlib_noexcept; -extern inline bson_doc _bson_new_reserve_with_default_alloc(uint32_t n) mlib_noexcept; -extern inline bson_doc _bson_copy_doc_with_allocator(bson_doc doc, - mlib_allocator alloc) mlib_noexcept; -extern inline bson_doc _bson_copy_view_with_allocator(bson_view view, - mlib_allocator alloc) mlib_noexcept; -extern inline bson_doc _bson_copy_mut_with_allocator(bson_mut m, - mlib_allocator alloc) mlib_noexcept; -extern inline void bson_delete(bson_doc d) mlib_noexcept; +extern inline bson_doc +_bson_copy_array_view_with_default_allocator(bson_array_view view) mlib_noexcept; +extern inline bson_doc _bson_copy_mut_with_default_allocator(bson_mut m) mlib_noexcept; +extern inline bson_doc _bson_new_reserve_with_default_alloc(uint32_t n) mlib_noexcept; +extern inline bson_doc _bson_copy_doc_with_allocator(bson_doc doc, + mlib_allocator alloc) mlib_noexcept; +extern inline bson_doc _bson_copy_view_with_allocator(bson_view view, + mlib_allocator alloc) mlib_noexcept; +extern inline bson_doc _bson_copy_array_view_with_allocator(bson_array_view view, + mlib_allocator alloc) mlib_noexcept; +extern inline bson_doc _bson_copy_mut_with_allocator(bson_mut m, + mlib_allocator alloc) mlib_noexcept; +extern mlib_constexpr void bson_delete(bson_doc d) mlib_noexcept; extern inline const bson_byte* _bson_get_global_empty_doc_data(void) mlib_noexcept; extern mlib_constexpr bson_byte* _bson_doc_buffer_ptr(bson_doc) mlib_noexcept; From d3eeadf5b635b77573fd2b05ec7855b8c0c5797f Mon Sep 17 00:00:00 2001 From: vector-of-bool Date: Wed, 18 Dec 2024 14:16:23 -0700 Subject: [PATCH 04/14] Support and simplify many more generic function signatures --- docs/how-to/looping.example.c | 12 ++--- docs/how-to/looping.rst | 6 +-- docs/ref/async.rst | 29 ++++++++++-- docs/ref/bson/value.rst | 11 +++-- include/amongoc/async.h | 86 +++++++++++++++++++++++++++-------- include/bson/doc.h | 8 ++-- include/bson/mut.h | 15 +++--- include/bson/value.h | 7 ++- include/mlib/config.h | 7 +++ include/mlib/str.h | 17 +++---- src/amongoc/async.just.cpp | 2 +- src/amongoc/collection.cpp | 4 +- 12 files changed, 133 insertions(+), 71 deletions(-) diff --git a/docs/how-to/looping.example.c b/docs/how-to/looping.example.c index dd7081b..3780a31 100644 --- a/docs/how-to/looping.example.c +++ b/docs/how-to/looping.example.c @@ -33,10 +33,8 @@ amongoc_emitter loop_step(amongoc_box state_ptr, amongoc_status prev_status, amo fprintf(stderr, "%d seconds remain, current value: %lu\n", s->countdown, cur); // Check if we are done if (s->countdown == 0) { - // No more looping to do. Return a final null result - return amongoc_just(amongoc_okay, - amongoc_box_uint64(cur), - amongoc_loop_get_allocator(s->loop)); + // No more looping to do. Return a final result + return amongoc_just(amongoc_box_uint64(cur)); } // Decrement the counter and start a sleep of one second --s->countdown; @@ -44,11 +42,7 @@ amongoc_emitter loop_step(amongoc_box state_ptr, amongoc_status prev_status, amo amongoc_emitter em = amongoc_schedule_later(s->loop, dur); // Connect the sleep to this function so that we will be called again after // the delay has elapsed. Return this as the new operation for the loop. - return amongoc_let(em, - amongoc_async_forward_errors, - amongoc_loop_get_allocator(s->loop), - state_ptr, - loop_step); + return amongoc_let(em, amongoc_async_forward_errors, state_ptr, loop_step); } // end. diff --git a/docs/how-to/looping.rst b/docs/how-to/looping.rst index 7e3679d..ae7cf0b 100644 --- a/docs/how-to/looping.rst +++ b/docs/how-to/looping.rst @@ -100,9 +100,9 @@ countdown reaches zero. :end-at: } The `amongoc_just` function creates a pseudo-async operation that resolves -immediately with the given result. Here, we create a successful status with -`amongoc_okay` and use `amongoc_box_uint64` to create a box that stores the -final calculation. This result value box will appear at the end of our loop. +immediately with the given result. Here, we use `amongoc_box_uint64` to create a +box that stores the final calculation. This result value box will appear at the +end of our loop. Starting a Timer diff --git a/docs/ref/async.rst b/docs/ref/async.rst index 5b711d9..4004a13 100644 --- a/docs/ref/async.rst +++ b/docs/ref/async.rst @@ -111,21 +111,37 @@ Asynchronous Continuations the result of another asynchronous operation. -Other -***** +Immediate Completion +******************** -.. function:: amongoc_emitter [[type(T)]] amongoc_just(amongoc_status st, amongoc_box [[transfer, type(T)]] value, mlib_allocator alloc) +.. function:: + [[1]] amongoc_emitter [[type(T)]] amongoc_just(amongoc_status st, amongoc_box [[transfer, type(T)]] value, mlib_allocator alloc) + [[2]] amongoc_emitter [[type(nil)]] amongoc_just(amongoc_status st) + [[3]] amongoc_emitter [[type(T)]] amongoc_just(amongoc_box [[transfer, type(T)]] value) + [[4]] amongoc_emitter [[type(T)]] amongoc_just(amongoc_status st, amongoc_box [[transfer, type(T)]] value) + [[5]] amongoc_emitter [[type(T)]] amongoc_just(amongoc_box [[transfer, type(T)]] value, mlib_allocator alloc) + [[6]] amongoc_emitter [[type(nil)]] amongoc_just() Create an emitter that will resolve immediately with the given status and result value. - :param st: The result status. - :param value: |attr.transfer| The result value. + :param st: The result status. If omitted, `amongoc_okay`. + :param value: |attr.transfer| The result value. If omitted, `amongoc_nil` :param alloc: |opstate-alloc| + :allocation: Signatures (2) and (6) do not allocate. Signatures (3) and (4) use `mlib_default_allocator`. :return: A new `amongoc_emitter` |R| whose result status will be `st` and result value will be `value` :header: |this-header| + .. rubric:: Overloads + + 1. Specify the status, the result value, and an allocator + 2. Specify only the status. The result value is `amongoc_nil`. This overload does not allocate any memory. + 3. Specify the result value. Uses the default allocator, with `amongoc_okay` status. + 4. Specify a result status and result value. Uses the default allocator. + 5. Specify a result value and an allocator, with `amongoc_okay` status + 6. Resolve with `amongoc_okay` and `amongoc_nil`. Does not allocate. + .. note:: The returned emitter here is not tied to any event loop, and it will call @@ -140,6 +156,9 @@ Other to the handler. +Other +***** + .. function:: amongoc_emitter [[type(T)]] amongoc_then_just( \ amongoc_emitter [[transfer]] em, \ diff --git a/docs/ref/bson/value.rst b/docs/ref/bson/value.rst index 775f0f7..857d395 100644 --- a/docs/ref/bson/value.rst +++ b/docs/ref/bson/value.rst @@ -172,7 +172,7 @@ Types - Result - - `__bson_viewable` - `bson_type_document` - - - `bson_array` + - - `bson_array_view` - `bson_type_array` - - `__string_convertible` - `bson_type_utf8` @@ -182,13 +182,13 @@ Types - `bson_type_oid` - - `bson_datetime` - `bson_type_datetime` - - - `bson_regex` + - - `bson_regex_view` - `bson_type_regex` - - - `bson_dbpointer` + - - `bson_dbpointer_view` - `bson_type_dbpointer` - - - `bson_code` + - - `bson_code_view` - `bson_type_code` - - - `bson_symbol` + - - `bson_symbol_view` - `bson_type_symbol` - - `int32_t` - `bson_type_int32` @@ -222,6 +222,7 @@ Functions & Macros .. function:: bson_value bson_value_copy(__bson_value_convertible V) + bson_value bson_value_copy(__bson_value_convertible V, mlib_allocator alloc) Create a copy of `V` stored in a dynamically typed `bson_value`. The returned value must eventually be destroyed. diff --git a/include/amongoc/async.h b/include/amongoc/async.h index 74e8a5b..9e9dd08 100644 --- a/include/amongoc/async.h +++ b/include/amongoc/async.h @@ -120,28 +120,30 @@ inline amongoc_emitter _amongoc_then_cxx(amongoc_emitter em, } #define amongoc_then(...) _amongoc_then_cxx(__VA_ARGS__) #else -#define amongoc_then(...) MLIB_PASTE(_amongocThenArgc_, MLIB_ARG_COUNT(__VA_ARGS__))(__VA_ARGS__) +#define amongoc_then(...) MLIB_ARGC_PICK(_amongocThen, __VA_ARGS__) // clang-format off // 2-args: An emitter and continuation -#define _amongocThenArgc_2(Emitter, Then) \ +#define _amongocThen_argc_2(Emitter, Then) \ amongoc_then((Emitter), amongoc_async_default, mlib_default_allocator, amongoc_nil, (Then)) // 3-args: One of: // - then(em, userdata, cb) // - then(em, flags, cb) -#define _amongocThenArgc_3(Emitter, UdOrFlags, Then) \ +#define _amongocThen_argc_3(Emitter, UdOrFlags, Then) \ mlib_generic(_amongoc_then_cxx, _amongoc_then_em_ud_cb, (UdOrFlags), \ amongoc_box: _amongoc_then_em_ud_cb, \ - enum amongoc_async_flags: _amongoc_then_em_fl_cb \ - )(Emitter, UdOrFlags, Then) + enum amongoc_async_flags: _amongoc_then_em_fl_cb, \ + int: _amongoc_then_em_fl_cb \ + )((Emitter), (UdOrFlags), (Then)) // 4-args: One of: // - then(em, alloc, userdata, cb) // - then(em, flags, userdata, cb) -#define _amongocThenArgc_4(Emitter, AllocOrFlags, Userdata, Then) \ +#define _amongocThen_argc_4(Emitter, AllocOrFlags, Userdata, Then) \ mlib_generic(_amongoc_then_cxx, _amongoc_then_em_al_ud_cb, (AllocOrFlags), \ mlib_allocator: _amongoc_then_em_al_ud_cb, \ - enum amongoc_async_flags: _amongoc_then_em_fl_ud_cb\ - )((Emitter, AllocOrFlags, Userdata, Then)) -#define _amongocThenArgc_5 amongoc_then + enum amongoc_async_flags: _amongoc_then_em_fl_ud_cb, \ + int: _amongoc_then_em_fl_ud_cb \ + )((Emitter), (AllocOrFlags), (Userdata), (Then)) +#define _amongocThen_argc_5 amongoc_then // clang-format on static inline amongoc_emitter _amongoc_then_em_ud_cb(amongoc_emitter em, amongoc_box userdata, @@ -224,28 +226,30 @@ inline amongoc_emitter _amongoc_let_cxx(amongoc_emitter em, } #define amongoc_let(...) _amongoc_let_cxx(__VA_ARGS__) #else -#define amongoc_let(...) MLIB_PASTE(_amongocLetArgc_, MLIB_ARG_COUNT(__VA_ARGS__))(__VA_ARGS__) +#define amongoc_let(...) MLIB_ARGC_PICK(_amongocLet, __VA_ARGS__) // clang-format off // 2-args: An emitter and continuation -#define _amongocLetArgc_2(Emitter, Then) \ - amongoc_let((Emitter), amongoc_async_default, mlib_default_allocator, amongoc_nil, (Then)) +#define _amongocLet_argc_2(Emitter, Let) \ + amongoc_let((Emitter), amongoc_async_default, mlib_default_allocator, amongoc_nil, (Let)) // 3-args: One of: // - then(em, userdata, cb) // - then(em, flags, cb) -#define _amongocLetArgc_3(Emitter, UdOrFlags, Then) \ +#define _amongocLet_argc_3(Emitter, UdOrFlags, Let) \ mlib_generic(_amongoc_let_cxx, _amongoc_let_em_ud_cb, (UdOrFlags), \ amongoc_box: _amongoc_let_em_ud_cb, \ - enum amongoc_async_flags: _amongoc_let_em_fl_cb \ - )(Emitter, UdOrFlags, Then) + enum amongoc_async_flags: _amongoc_let_em_fl_cb, \ + int: _amongoc_let_em_fl_cb \ + )((Emitter), (UdOrFlags), (Let)) // 4-args: One of: // - then(em, alloc, userdata, cb) // - then(em, flags, userdata, cb) -#define _amongocLetArgc_4(Emitter, AllocOrFlags, Userdata, Then) \ +#define _amongocLet_argc_4(Emitter, AllocOrFlags, Userdata, Let) \ mlib_generic(_amongoc_let_cxx, _amongoc_let_em_al_ud_cb, (AllocOrFlags), \ mlib_allocator: _amongoc_let_em_al_ud_cb, \ - enum amongoc_async_flags: _amongoc_let_em_fl_ud_cb\ - )((Emitter, AllocOrFlags, Userdata, Then)) -#define _amongocLetArgc_5 amongoc_let + enum amongoc_async_flags: _amongoc_let_em_fl_ud_cb, \ + int: _amongoc_let_em_fl_ud_cb \ + )((Emitter), (AllocOrFlags), (Userdata), (Let)) +#define _amongocLet_argc_5 amongoc_let // clang-format on static inline amongoc_emitter _amongoc_let_em_ud_cb(amongoc_emitter em, amongoc_box userdata, @@ -282,6 +286,50 @@ static inline amongoc_emitter _amongoc_let_em_fl_ud_cb(amongoc_emitter amongoc_emitter amongoc_just(amongoc_status st, amongoc_box value, mlib_allocator alloc) mlib_noexcept; +#define amongoc_just(...) MLIB_ARGC_PICK(_amongocJust, __VA_ARGS__) +#define _amongocJust_argc_0(...) amongoc_just(amongoc_okay, amongoc_nil, mlib_default_allocator) +// clang-format off +#define _amongocJust_argc_1(Arg) \ + mlib_generic(_amongoc_just_generic, amongoc_just, (Arg), \ + amongoc_status: _amongoc_just_status, \ + amongoc_box: _amongoc_just_value)(Arg) +#define _amongocJust_argc_2(FirstArg, SecondArg) \ + mlib_generic(_amongoc_just_generic, amongoc_just, (FirstArg), \ + amongoc_status: _amongoc_just_st_value, \ + amongoc_box: _amongoc_just_val_alloc)((FirstArg), (SecondArg)) +#define _amongocJust_argc_3(Status, Value, Alloc) amongoc_just((Status), (Value), (Alloc)) +// clang-format on +static inline amongoc_emitter _amongoc_just_status(amongoc_status st) mlib_noexcept { + return amongoc_just(st, amongoc_nil, mlib_default_allocator); +} +static inline amongoc_emitter _amongoc_just_value(amongoc_box val) mlib_noexcept { + return amongoc_just(amongoc_okay, val, mlib_default_allocator); +} +static inline amongoc_emitter _amongoc_just_st_value(amongoc_status st, + amongoc_box value) mlib_noexcept { + return amongoc_just(st, value, mlib_default_allocator); +} +static inline amongoc_emitter _amongoc_just_val_alloc(amongoc_box value, + mlib_allocator alloc) mlib_noexcept { + return amongoc_just(amongoc_okay, value, alloc); +} +#if mlib_is_cxx() +extern "C++" { +inline amongoc_emitter _amongoc_just_generic(amongoc_status st, amongoc_box value) noexcept { + return ::amongoc_just(st, value, ::mlib_default_allocator); +} +inline amongoc_emitter _amongoc_just_generic(amongoc_box value, mlib_allocator alloc) noexcept { + return ::amongoc_just(amongoc_okay, value, alloc); +} +inline amongoc_emitter _amongoc_just_generic(amongoc_status st) noexcept { + return ::amongoc_just(st, ::amongoc_nil, ::mlib_default_allocator); +} +inline amongoc_emitter _amongoc_just_generic(amongoc_box value) noexcept { + return ::amongoc_just(::amongoc_okay, value, ::mlib_default_allocator); +} +} +#endif + /** * @brief Create a continuation that replaces an emitter's result with the given * status and result value diff --git a/include/bson/doc.h b/include/bson/doc.h index 374c59c..b721fa5 100644 --- a/include/bson/doc.h +++ b/include/bson/doc.h @@ -222,10 +222,10 @@ typedef struct bson_mut { #if mlib_is_cxx() #define _bsonNew(...) _bson_new_generic(__VA_ARGS__) #else -#define _bsonNew(...) MLIB_PASTE(_bsonNewArgc_, MLIB_ARG_COUNT(__VA_ARGS__))(__VA_ARGS__) +#define _bsonNew(...) MLIB_ARGC_PICK(_bsonNew, __VA_ARGS__) #endif -#define _bsonNewArgc_0() _bson_new(5, mlib_default_allocator) -#define _bsonNewArgc_1(X) \ +#define _bsonNew_argc_0() _bson_new(5, mlib_default_allocator) +#define _bsonNew_argc_1(X) \ _Generic(X, \ mlib_allocator: _bson_new_with_alloc, \ bson_doc: _bson_copy_doc, \ @@ -233,7 +233,7 @@ typedef struct bson_mut { bson_array_view: _bson_copy_array_view_with_default_allocator, \ struct bson_mut: _bson_copy_mut_with_default_allocator, \ default: _bson_new_reserve_with_default_alloc)(X) -#define _bsonNewArgc_2(ReserveOrDoc, Alloc) \ +#define _bsonNew_argc_2(ReserveOrDoc, Alloc) \ _Generic((ReserveOrDoc), \ bson_doc: _bson_copy_doc_with_allocator, \ bson_view: _bson_copy_view_with_allocator, \ diff --git a/include/bson/mut.h b/include/bson/mut.h index 48a7aec..a71d34b 100644 --- a/include/bson/mut.h +++ b/include/bson/mut.h @@ -321,12 +321,9 @@ inline bson_iterator bson_insert_code_with_scope(bson_mut* doc, * - `bson_insert(bson_mut, string-like, value-like)` * - `bson_insert(bson_mut, bson_iterator, string-like, value-like)` */ -#define bson_insert(...) _bsonInsert(__VA_ARGS__) -#define _bsonInsert(...) MLIB_PASTE(_bsonInsertArgc_, MLIB_ARG_COUNT(__VA_ARGS__))(__VA_ARGS__) - -#define _bsonInsertArgc_3(Mut, Key, Value) _bsonInsertAt(Mut, bson_end(*(Mut)), (Key), Value) -#define _bsonInsertArgc_4(Mut, Pos, Key, Value) _bsonInsertAt(Mut, (Pos), (Key), Value) - +#define bson_insert(...) MLIB_ARGC_PICK(_bsonInsert, __VA_ARGS__) +#define _bsonInsert_argc_3(Mut, Key, Value) _bsonInsertAt(Mut, bson_end(*(Mut)), (Key), Value) +#define _bsonInsert_argc_4(Mut, Pos, Key, Value) _bsonInsertAt(Mut, (Pos), (Key), Value) #define _bsonInsertAt(Mut, Position, Key, Value) \ _bson_insert_value((Mut), (Position), mlib_str_view_from(Key), bson_value_ref_from(Value)) @@ -530,9 +527,9 @@ inline bson_iterator bson_erase_one(bson_mut* const doc, const bson_iterator pos return bson_erase_range(doc, pos, bson_next(pos)); } -#define bson_erase(...) MLIB_PASTE(_bsonEraseArgc_, MLIB_ARG_COUNT(__VA_ARGS__))(__VA_ARGS__) -#define _bsonEraseArgc_2(Doc, Pos) bson_erase_one((Doc), (Pos)) -#define _bsonEraseArgc_3(Doc, First, Last) bson_erase_range((Doc), (First), (Last)) +#define bson_erase(...) MLIB_ARGC_PICK(_bsonErase, __VA_ARGS__) +#define _bsonErase_argc_2(Doc, Pos) bson_erase_one((Doc), (Pos)) +#define _bsonErase_argc_3(Doc, First, Last) bson_erase_range((Doc), (First), (Last)) /** * @brief Obtain a mutator for the subdocument at the given position within diff --git a/include/bson/value.h b/include/bson/value.h index 95dffd1..df883a8 100644 --- a/include/bson/value.h +++ b/include/bson/value.h @@ -247,10 +247,9 @@ mlib_constexpr bson_value_ref _bson_value_ref_from_value(bson_value val) mlib_no bson_value: _bson_value_ref_from_value, \ bson_value_ref: _bson_value_ref_dup)((X)) -#define bson_value_copy(...) \ - MLIB_PASTE(_bsonValueCopyArgc_, MLIB_ARG_COUNT(__VA_ARGS__))(__VA_ARGS__) -#define _bsonValueCopyArgc_1(X) _bson_value_copy(bson_value_ref_from((X)), mlib_default_allocator) -#define _bsonValueCopyArgc_2(X, Alloc) _bson_value_copy(bson_value_ref_from((X)), (Alloc)) +#define bson_value_copy(...) MLIB_ARGC_PICK(_bsonValueCopy, __VA_ARGS__) +#define _bsonValueCopy_argc_1(X) _bson_value_copy(bson_value_ref_from((X)), mlib_default_allocator) +#define _bsonValueCopy_argc_2(X, Alloc) _bson_value_copy(bson_value_ref_from((X)), (Alloc)) static mlib_constexpr bson_value _bson_value_copy(bson_value_ref val, mlib_allocator alloc) mlib_noexcept { bson_value ret MLIB_IF_CXX(= {}); diff --git a/include/mlib/config.h b/include/mlib/config.h index a6e0a14..5ec557f 100644 --- a/include/mlib/config.h +++ b/include/mlib/config.h @@ -320,6 +320,13 @@ #define mlib_empty_aggregate_c_compat \ MLIB_LANG_PICK(char _placeholder)(static_assert(true, "")) +/** + * @brief Expand to a call expression `Prefix##_argc_N(...)`, where `N` is the + * number of macro arguments. + */ +#define MLIB_ARGC_PICK(Prefix, ...) \ + MLIB_PASTE_3(Prefix, _argc_, MLIB_ARG_COUNT(__VA_ARGS__))(__VA_ARGS__) + #if mlib_is_cxx() namespace mlib { diff --git a/include/mlib/str.h b/include/mlib/str.h index 421a8ab..34a95c6 100644 --- a/include/mlib/str.h +++ b/include/mlib/str.h @@ -342,10 +342,10 @@ inline bool mlib_str_mut_resize(mlib_str_mut* s, size_t new_len) mlib_noexcept { * @note The @ref mlib_str_mut::str member MUST eventually be given to * @ref mlib_str_delete(). */ -#define mlib_str_new(...) MLIB_PASTE(_mlibStrNewArgc_, MLIB_ARG_COUNT(__VA_ARGS__))(__VA_ARGS__) -#define _mlibStrNewArgc_0() _mlib_str_new(0, mlib_default_allocator) -#define _mlibStrNewArgc_1(N) _mlib_str_new(N, mlib_default_allocator) -#define _mlibStrNewArgc_2(N, Alloc) _mlib_str_new(N, Alloc) +#define mlib_str_new(...) MLIB_ARGC_PICK(_mlibStrNew, __VA_ARGS__) +#define _mlibStrNew_argc_0() _mlib_str_new(0, mlib_default_allocator) +#define _mlibStrNew_argc_1(N) _mlib_str_new(N, mlib_default_allocator) +#define _mlibStrNew_argc_2(N, Alloc) _mlib_str_new(N, Alloc) inline mlib_str_mut _mlib_str_new(size_t len, mlib_allocator alloc) mlib_noexcept { mlib_str_mut ret; @@ -381,12 +381,9 @@ mlib_str_copy_data(const char* s, size_t len, mlib_allocator alloc) mlib_noexcep * @param s A string view to copy from * @return mlib_str A new string copied from the given view */ -#define mlib_str_copy(...) \ - MLIB_PASTE(_mlibStrCopyArgc_, MLIB_ARG_COUNT(__VA_ARGS__)) \ - (__VA_ARGS__) - -#define _mlibStrCopyArgc_1(S) _mlib_str_copy(mlib_str_view_from((S)), mlib_default_allocator) -#define _mlibStrCopyArgc_2(S, Alloc) _mlib_str_copy(mlib_str_view_from((S)), Alloc) +#define mlib_str_copy(...) MLIB_ARGC_PICK(_mlibStrCopy, __VA_ARGS__) +#define _mlibStrCopy_argc_1(S) _mlib_str_copy(mlib_str_view_from((S)), mlib_default_allocator) +#define _mlibStrCopy_argc_2(S, Alloc) _mlib_str_copy(mlib_str_view_from((S)), Alloc) inline mlib_str_mut _mlib_str_copy(mlib_str_view s, mlib_allocator alloc) mlib_noexcept { return mlib_str_copy_data(s.data, s.len, alloc); } diff --git a/src/amongoc/async.just.cpp b/src/amongoc/async.just.cpp index 1d9ef1f..5931220 100644 --- a/src/amongoc/async.just.cpp +++ b/src/amongoc/async.just.cpp @@ -4,7 +4,7 @@ using namespace amongoc; -emitter amongoc_just(status st, box value, mlib_allocator alloc_) noexcept { +emitter(amongoc_just)(status st, box value, mlib_allocator alloc_) noexcept { // Make unique outside of just_1 to reduce code size of just_1 auto&& uniq = mlib_fwd(value).as_unique(); mlib::allocator<> alloc{alloc_}; diff --git a/src/amongoc/collection.cpp b/src/amongoc/collection.cpp index 53fd492..8b6c73f 100644 --- a/src/amongoc/collection.cpp +++ b/src/amongoc/collection.cpp @@ -448,8 +448,8 @@ static bool _is_update_spec_doc(bson_view s) { } static emitter _invalid_update_doc_em(mlib::allocator<> a) { - return amongoc_just(amongoc_status{&amongoc_client_category, - ::amongoc_client_errc_invalid_update_document}, + return amongoc_just((amongoc_status{&amongoc_client_category, + ::amongoc_client_errc_invalid_update_document}), amongoc_nil, a.c_allocator()); } From 4420d498c604142ecfeba41a1941d75c1c9f83e9 Mon Sep 17 00:00:00 2001 From: vector-of-bool Date: Wed, 18 Dec 2024 14:17:01 -0700 Subject: [PATCH 05/14] Tweak defs of some BSON generic functions to cast-to-view first --- include/bson/view.h | 80 ++++++++++++++++++++++++++++----------------- 1 file changed, 50 insertions(+), 30 deletions(-) diff --git a/include/bson/view.h b/include/bson/view.h index eaf816c..d8d1477 100644 --- a/include/bson/view.h +++ b/include/bson/view.h @@ -17,31 +17,6 @@ typedef struct bson_view bson_view; struct bson_iterator; -/** - * @brief Obtain a pointer to the beginning of the data for the given document. - */ -#define bson_data(X) _bson_data_as_const((X)._bson_document_data) - -/** - * @brief Obtain a mutable pointer to the beginning of the data for the given - * document. - * - * @note This only works if the object is a mutable document reference (e.g. not - * bson_view) - */ -#define bson_mut_data(X) _bson_data_as_mut((X)._bson_document_data) - -/** - * @brief Obtain the byte-size of the BSON document referred to by the given - * bson_view. - */ -#define bson_size(...) _bson_byte_size(bson_data((__VA_ARGS__))) - -/** - * @brief Obtain the byte-size of the given BSON document, as a signed integer - */ -#define bson_ssize(...) _bson_byte_ssize(bson_data((__VA_ARGS__))) - /** * @brief A type specifically representing a nullable read-only view of a BSON * document. @@ -64,12 +39,26 @@ typedef struct bson_view { using size_type = std::uint32_t; // Return a pointer to the document data - [[nodiscard]] inline const bson_byte* data() const noexcept { return ::bson_data(*this); } + [[nodiscard]] inline const bson_byte* data() const noexcept { + return this->_bson_document_data; + } /** * @brief Obtain the size of the document data, in bytes */ - [[nodiscard]] inline size_type byte_size() const noexcept { return ::bson_size(*this); } + [[nodiscard]] inline size_type byte_size() const noexcept { + return ::_bson_byte_size(this->data()); + } + + /** + * @brief Handle any type that is explicit-convertible to a view. + * + * The `static_cast` in the return type will SFINAE-away invalid operands + */ + template + [[nodiscard]] constexpr static auto from(T o) noexcept -> decltype(static_cast(o)) { + return static_cast(o); + } #if mlib_have_cxx20() /** @@ -166,12 +155,16 @@ typedef struct bson_array_view { } // Return a pointer to the array data - [[nodiscard]] inline const bson_byte* data() const noexcept { return ::bson_data(*this); } + [[nodiscard]] inline const bson_byte* data() const noexcept { + return this->_bson_document_data; + } /** * @brief Obtain the size of the array data, in bytes */ - [[nodiscard]] inline size_type byte_size() const noexcept { return ::bson_size(*this); } + [[nodiscard]] inline size_type byte_size() const noexcept { + return ::_bson_byte_size(this->data()); + } #if mlib_have_cxx20() [[nodiscard]] std::span bytes() const noexcept; @@ -278,7 +271,9 @@ inline bson_view bson_view_from_data(const bson_byte* const data, /** * @brief Obtain a bson_view for the given document-like entity. */ -#define bson_view_from(...) _bson_view_from_ptr(bson_data(__VA_ARGS__)) +#define bson_view_from(...) \ + MLIB_LANG_PICK(_bson_view_from_ptr((__VA_ARGS__)._bson_document_data)) \ + (bson_view::from(__VA_ARGS__)) inline bson_view _bson_view_from_ptr(const bson_byte* p) mlib_noexcept { if (!p) { return bson_view_null; @@ -288,6 +283,31 @@ inline bson_view _bson_view_from_ptr(const bson_byte* p) mlib_noexcept { return bson_view_from_data(p, (uint32_t)len, NULL); } +/** + * @brief Obtain a pointer to the beginning of the data for the given document. + */ +#define bson_data(X) _bson_data_as_const(bson_view_from(X)._bson_document_data) + +/** + * @brief Obtain a mutable pointer to the beginning of the data for the given + * document. + * + * @note This only works if the object is a mutable document reference (e.g. not + * bson_view) + */ +#define bson_mut_data(X) _bson_data_as_mut((X)._bson_document_data) + +/** + * @brief Obtain the byte-size of the BSON document referred to by the given + * bson_view. + */ +#define bson_size(...) _bson_byte_size(bson_data((__VA_ARGS__))) + +/** + * @brief Obtain the byte-size of the given BSON document, as a signed integer + */ +#define bson_ssize(...) _bson_byte_ssize(bson_data((__VA_ARGS__))) + mlib_extern_c_end(); #if mlib_is_cxx() From 2b6d549b04314c873baf28a92f4857dca153db61 Mon Sep 17 00:00:00 2001 From: vector-of-bool Date: Wed, 18 Dec 2024 14:17:31 -0700 Subject: [PATCH 06/14] Add compile-time tests for our generic function signatures --- CMakeLists.txt | 7 ++ tests/sigcheck.test.c | 1 + tests/sigcheck.test.cpp | 1 + tests/sigcheck.test.h | 205 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 214 insertions(+) create mode 100644 tests/sigcheck.test.c create mode 100644 tests/sigcheck.test.cpp create mode 100644 tests/sigcheck.test.h diff --git a/CMakeLists.txt b/CMakeLists.txt index fefb482..a3c320f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -156,6 +156,13 @@ if(BUILD_TESTING) include(GenURITests) target_sources(amongoc-test PRIVATE ${URITest_SOURCES}) + # Add compile-time tests for generic function signatures + target_sources(amongoc-test PRIVATE + tests/sigcheck.test.cpp + tests/sigcheck.test.c + tests/sigcheck.test.h + ) + # Don't add the testproject tests if we are already building as a test project if(NOT DEFINED HOST_PROJECT_CMAKE_SOURCE_DIR) include(TestProject) diff --git a/tests/sigcheck.test.c b/tests/sigcheck.test.c new file mode 100644 index 0000000..5a251a8 --- /dev/null +++ b/tests/sigcheck.test.c @@ -0,0 +1 @@ +#include "./sigcheck.test.h" diff --git a/tests/sigcheck.test.cpp b/tests/sigcheck.test.cpp new file mode 100644 index 0000000..5a251a8 --- /dev/null +++ b/tests/sigcheck.test.cpp @@ -0,0 +1 @@ +#include "./sigcheck.test.h" diff --git a/tests/sigcheck.test.h b/tests/sigcheck.test.h new file mode 100644 index 0000000..c1e0780 --- /dev/null +++ b/tests/sigcheck.test.h @@ -0,0 +1,205 @@ +#pragma once + +#include "amongoc/async.h" +#include "amongoc/box.h" +#include "amongoc/emitter.h" +#include "amongoc/status.h" +#include + +#include "bson/iterator.h" +#include "bson/mut.h" +#include "bson/types.h" +#include "bson/value.h" +#include "bson/value_ref.h" +#include "bson/view.h" + +#include "mlib/alloc.h" + +#if mlib_is_cxx() +#include +#endif + +mlib_extern_c_begin(); + +#define GLOBAL_SCOPE MLIB_IF_CXX(::) + +static inline void amongoc_test_all_signatures() { + // bson_new + bson_view some_bson_view = bson_view_null; + bson_array_view some_bson_array = bson_array_view_null; + bson_doc some_bson_doc = bson_new(); + bson_mut some_bson_mut = bson_mutate(&some_bson_doc); + + some_bson_doc = GLOBAL_SCOPE bson_new(42, mlib_default_allocator); + some_bson_doc = GLOBAL_SCOPE bson_new(some_bson_view, mlib_default_allocator); + some_bson_doc = GLOBAL_SCOPE bson_new(some_bson_doc, mlib_default_allocator); + some_bson_doc = GLOBAL_SCOPE bson_new(some_bson_mut, mlib_default_allocator); + some_bson_doc = GLOBAL_SCOPE bson_new(some_bson_array, mlib_default_allocator); + some_bson_doc = GLOBAL_SCOPE bson_new(); + some_bson_doc = GLOBAL_SCOPE bson_new(42); + some_bson_doc = GLOBAL_SCOPE bson_new(mlib_default_allocator); + some_bson_doc = GLOBAL_SCOPE bson_new(some_bson_view); + some_bson_doc = GLOBAL_SCOPE bson_new(some_bson_doc); + some_bson_doc = GLOBAL_SCOPE bson_new(some_bson_mut); + some_bson_doc = GLOBAL_SCOPE bson_new(some_bson_array); + const bson_byte* some_cbyte_ptr; + (void)some_cbyte_ptr; + some_cbyte_ptr = GLOBAL_SCOPE bson_data(some_bson_view); + some_cbyte_ptr = GLOBAL_SCOPE bson_data(some_bson_doc); + some_cbyte_ptr = GLOBAL_SCOPE bson_data(some_bson_mut); + some_cbyte_ptr = GLOBAL_SCOPE bson_data(some_bson_array); + bson_byte* some_mbyte_ptr; + (void)some_mbyte_ptr; + some_mbyte_ptr = GLOBAL_SCOPE bson_mut_data(some_bson_doc); + some_mbyte_ptr = GLOBAL_SCOPE bson_mut_data(some_bson_mut); + + uint32_t some_u32; + some_u32 = GLOBAL_SCOPE bson_size(some_bson_view); + some_u32 = GLOBAL_SCOPE bson_size(some_bson_doc); + some_u32 = GLOBAL_SCOPE bson_size(some_bson_mut); + some_u32 = GLOBAL_SCOPE bson_size(some_bson_array); + (void)some_u32; + int32_t some_i32; + some_i32 = GLOBAL_SCOPE bson_ssize(some_bson_view); + some_i32 = GLOBAL_SCOPE bson_ssize(some_bson_doc); + some_i32 = GLOBAL_SCOPE bson_ssize(some_bson_mut); + some_i32 = GLOBAL_SCOPE bson_ssize(some_bson_array); + (void)some_i32; + // bson_view_from + some_bson_view = GLOBAL_SCOPE bson_view_from(some_bson_view); + some_bson_view = GLOBAL_SCOPE bson_view_from(some_bson_array); + some_bson_view = GLOBAL_SCOPE bson_view_from(some_bson_doc); + some_bson_view = GLOBAL_SCOPE bson_view_from(some_bson_mut); +#if mlib_is_cxx() + bson::document cxx_doc{::mlib_default_allocator}; + bson::mutator cxx_mut{cxx_doc}; + some_bson_doc = ::bson_new(cxx_doc); + some_bson_doc = ::bson_new(cxx_doc, ::mlib_default_allocator); + some_bson_doc = ::bson_new(cxx_mut); + some_bson_doc = ::bson_new(cxx_mut, ::mlib_default_allocator); + some_bson_view = ::bson_view_from(cxx_doc); + some_bson_view = ::bson_view_from(cxx_mut); + some_cbyte_ptr = ::bson_data(cxx_mut); + some_cbyte_ptr = ::bson_data(cxx_doc); + some_u32 = ::bson_size(cxx_doc); + some_u32 = ::bson_size(cxx_mut); + some_i32 = ::bson_ssize(cxx_doc); + some_i32 = ::bson_ssize(cxx_mut); +#endif + + // bson_value_ref_from + bson_value_ref bref; + (void)bref; + bref = GLOBAL_SCOPE bson_value_ref_from(1.2); + bref = GLOBAL_SCOPE bson_value_ref_from(1.2f); + bref = GLOBAL_SCOPE bson_value_ref_from((int8_t)42); + bref = GLOBAL_SCOPE bson_value_ref_from((uint8_t)42); + bref = GLOBAL_SCOPE bson_value_ref_from((int16_t)42); + bref = GLOBAL_SCOPE bson_value_ref_from((uint16_t)42); + bref = GLOBAL_SCOPE bson_value_ref_from((int32_t)42); + bref = GLOBAL_SCOPE bson_value_ref_from((uint32_t)42); + bref = GLOBAL_SCOPE bson_value_ref_from((int64_t)42); + /// ! This one intentionally fails, since such a conversion would cause narrowing: + // bref = GLOBAL_SCOPE bson_value_ref_from((uint64_t)42); + bson_value bval; + (void)bval; + bref = GLOBAL_SCOPE bson_value_ref_from(bval); + bref = GLOBAL_SCOPE bson_value_ref_from(bref); + bref = GLOBAL_SCOPE bson_value_ref_from(some_bson_view); + bref = GLOBAL_SCOPE bson_value_ref_from(some_bson_doc); + bref = GLOBAL_SCOPE bson_value_ref_from(some_bson_mut); + bref = GLOBAL_SCOPE bson_value_ref_from(some_bson_array); + bref = GLOBAL_SCOPE bson_value_ref_from("hey"); + mlib_str some_string; + const char* cstr_ptr = "hey"; + mlib_str_view some_string_view; + mlib_str_mut some_string_mut; + bref = GLOBAL_SCOPE bson_value_ref_from(cstr_ptr); + bref = GLOBAL_SCOPE bson_value_ref_from(some_string); + bref = GLOBAL_SCOPE bson_value_ref_from(some_string_view); + bref = GLOBAL_SCOPE bson_value_ref_from(some_string_mut); +#if mlib_is_cxx() + std::string std_string; + std::string_view std_string_view; + bref = ::bson_value_ref_from(std_string); + bref = ::bson_value_ref_from(std_string_view); + bref = ::bson_value_ref_from(cxx_doc); + bref = ::bson_value_ref_from(cxx_mut); +#endif + bson_binary_view some_binary; + bref = GLOBAL_SCOPE bson_value_ref_from(some_binary); + bson_oid some_oid; + bref = GLOBAL_SCOPE bson_value_ref_from(some_oid); + bson_datetime some_datetime; + bref = GLOBAL_SCOPE bson_value_ref_from(some_datetime); + bson_regex_view some_regex_view; + bref = GLOBAL_SCOPE bson_value_ref_from(some_regex_view); + bson_dbpointer_view some_dbpointer; + bref = GLOBAL_SCOPE bson_value_ref_from(some_dbpointer); + bson_code_view some_code; + bref = GLOBAL_SCOPE bson_value_ref_from(some_code); + bson_symbol_view some_symbol; + bref = GLOBAL_SCOPE bson_value_ref_from(some_symbol); + bson_timestamp some_timestamp; + bref = GLOBAL_SCOPE bson_value_ref_from(some_timestamp); + bson_decimal128 some_decimal; + bref = GLOBAL_SCOPE bson_value_ref_from(some_decimal); + + // ? bson_value_copy does not need checking because it is written in terms of + // ? bson_value_ref_from + + amongoc_emitter some_emitter; + amongoc_box some_userdata = amongoc_nil; + enum amongoc_async_flags some_aflags = amongoc_async_default; + amongoc_box (*then_fn)(amongoc_box userdata, amongoc_status* status, amongoc_box result) = NULL; + amongoc_emitter (*let_fn)(amongoc_box userdata, amongoc_status status, amongoc_box result) + = NULL; + some_emitter = GLOBAL_SCOPE amongoc_then(some_emitter, then_fn); + some_emitter = GLOBAL_SCOPE amongoc_let(some_emitter, let_fn); + some_emitter = GLOBAL_SCOPE amongoc_then(some_emitter, some_userdata, then_fn); + some_emitter = GLOBAL_SCOPE amongoc_let(some_emitter, some_userdata, let_fn); + some_emitter = GLOBAL_SCOPE amongoc_then(some_emitter, amongoc_async_default, then_fn); + some_emitter = GLOBAL_SCOPE amongoc_let(some_emitter, amongoc_async_default, let_fn); + some_emitter = GLOBAL_SCOPE amongoc_then(some_emitter, some_aflags, then_fn); + some_emitter = GLOBAL_SCOPE amongoc_let(some_emitter, some_aflags, let_fn); + some_emitter + = GLOBAL_SCOPE amongoc_then(some_emitter, amongoc_async_default, some_userdata, then_fn); + some_emitter + = GLOBAL_SCOPE amongoc_let(some_emitter, amongoc_async_default, some_userdata, let_fn); + some_emitter = GLOBAL_SCOPE amongoc_then(some_emitter, some_aflags, some_userdata, then_fn); + some_emitter = GLOBAL_SCOPE amongoc_let(some_emitter, some_aflags, some_userdata, let_fn); + some_emitter + = GLOBAL_SCOPE amongoc_then(some_emitter, mlib_default_allocator, some_userdata, then_fn); + some_emitter + = GLOBAL_SCOPE amongoc_let(some_emitter, mlib_default_allocator, some_userdata, let_fn); + some_emitter = GLOBAL_SCOPE amongoc_then(some_emitter, + amongoc_async_default, + mlib_default_allocator, + some_userdata, + then_fn); + some_emitter = GLOBAL_SCOPE amongoc_let(some_emitter, + amongoc_async_default, + mlib_default_allocator, + some_userdata, + let_fn); + some_emitter = GLOBAL_SCOPE amongoc_then(some_emitter, + some_aflags, + mlib_default_allocator, + some_userdata, + then_fn); + some_emitter = GLOBAL_SCOPE amongoc_let(some_emitter, + some_aflags, + mlib_default_allocator, + some_userdata, + let_fn); + + // just() + some_emitter = GLOBAL_SCOPE amongoc_just(amongoc_okay, amongoc_nil, mlib_default_allocator); + some_emitter = GLOBAL_SCOPE amongoc_just(amongoc_okay); + some_emitter = GLOBAL_SCOPE amongoc_just(amongoc_nil); + some_emitter = GLOBAL_SCOPE amongoc_just(amongoc_okay, amongoc_nil); + some_emitter = GLOBAL_SCOPE amongoc_just(amongoc_nil, mlib_default_allocator); + some_emitter = GLOBAL_SCOPE amongoc_just(); +} + +mlib_extern_c_end(); From 089f715f961737fc11b120778797a0f3c3573f33 Mon Sep 17 00:00:00 2001 From: vector-of-bool Date: Wed, 18 Dec 2024 14:18:15 -0700 Subject: [PATCH 07/14] Rename the "connect" how-to to "communicate" This is to avoid collision with the simpler "connect" tutorial --- ...onnect.example.c => communicate.example.c} | 7 +--- docs/how-to/{connect.rst => communicate.rst} | 40 +++++++++---------- docs/how-to/index.rst | 2 +- 3 files changed, 22 insertions(+), 27 deletions(-) rename docs/how-to/{connect.example.c => communicate.example.c} (95%) rename docs/how-to/{connect.rst => communicate.rst} (90%) diff --git a/docs/how-to/connect.example.c b/docs/how-to/communicate.example.c similarity index 95% rename from docs/how-to/connect.example.c rename to docs/how-to/communicate.example.c index 4964ba0..d81d31a 100644 --- a/docs/how-to/connect.example.c +++ b/docs/how-to/communicate.example.c @@ -60,11 +60,7 @@ amongoc_emitter after_connect_say_hello(amongoc_box state_ptr, amongoc_status, a bson_view_from(mut)); bson_delete(doc); - em = amongoc_then(em, - amongoc_async_forward_errors, - mlib_default_allocator, - state_ptr, - after_hello); + em = amongoc_then(em, amongoc_async_forward_errors, state_ptr, after_hello); return em; } // end. @@ -89,7 +85,6 @@ int main(int argc, char const* const* argv) { em = amongoc_let(em, amongoc_async_forward_errors, - mlib_default_allocator, amongoc_box_pointer(&state), after_connect_say_hello); diff --git a/docs/how-to/connect.rst b/docs/how-to/communicate.rst similarity index 90% rename from docs/how-to/connect.rst rename to docs/how-to/communicate.rst index cca3dff..a82afb2 100644 --- a/docs/how-to/connect.rst +++ b/docs/how-to/communicate.rst @@ -1,11 +1,11 @@ -################### -Connect to a Server -################### +########################### +Communicating with a Server +########################### This how-to guide will walk through the follow example program: -.. literalinclude:: connect.example.c - :caption: ``connect.example.c`` +.. literalinclude:: communicate.example.c + :caption: ``communicate.example.c`` :linenos: Headers @@ -13,7 +13,7 @@ Headers We first include the "everything" library header: -.. literalinclude:: connect.example.c +.. literalinclude:: communicate.example.c :caption: Header file inclusions :lineno-match: :start-at: #include @@ -29,7 +29,7 @@ Command-Line Arguments The first action we perform in ``main()`` is checking our arguments: -.. literalinclude:: connect.example.c +.. literalinclude:: communicate.example.c :caption: Argument checking :lineno-match: :start-at: int main @@ -43,7 +43,7 @@ Initializing the Loop The first "interesting" code will declare and initialize the default event loop: -.. literalinclude:: connect.example.c +.. literalinclude:: communicate.example.c :lineno-match: :start-at: loop; :end-at: ); @@ -62,7 +62,7 @@ Declare the App State We use a type ``app_state`` to store some state that is shared across the application: -.. literalinclude:: connect.example.c +.. literalinclude:: communicate.example.c :lineno-match: :start-at: struct app_state :end-at: } app_state; @@ -71,7 +71,7 @@ This state needs to be stored in a way that it outlives the scope of each sub-operation in the program. For this reason, we declare the instance in ``main()``: -.. literalinclude:: connect.example.c +.. literalinclude:: communicate.example.c :lineno-match: :start-at: struct app_state state = :end-at: struct app_state state = @@ -89,7 +89,7 @@ Create a Client with a Timeout We create a connect operation using `amongoc_client_new`, and then attach a timeout using `amongoc_timeout` -.. literalinclude:: connect.example.c +.. literalinclude:: communicate.example.c :lineno-match: :start-at: client_new :end-at: amongoc_timeout @@ -102,7 +102,7 @@ and preferred for building composed asynchronous operations. Attach the First Continuation ############################# -.. literalinclude:: connect.example.c +.. literalinclude:: communicate.example.c :lineno-match: :start-at: amongoc_let :end-at: say_hello); @@ -122,7 +122,7 @@ The First Continuation The first step, after connecting to a server, is ``after_connect_say_hello``, a continuation function given to `amongoc_let`: -.. literalinclude:: connect.example.c +.. literalinclude:: communicate.example.c :lineno-match: :start-at: ** after_connect :end-at: ) { @@ -138,7 +138,7 @@ Upon success, the operation from `amongoc_client_new` will resolve with an `amongoc_client` in its boxed result value. We move the connection by-value from the box and store it in our application state: -.. literalinclude:: connect.example.c +.. literalinclude:: communicate.example.c :lineno-match: :start-at: after_connect_say_hello(amongoc_box :end-at: take @@ -150,7 +150,7 @@ object that was owned by the box is now owned by the storage destination. .. rubric:: Build and Prepare a Command -.. literalinclude:: connect.example.c +.. literalinclude:: communicate.example.c :lineno-match: :start-at: Create a "hello" command :end-at: bson_delete @@ -163,7 +163,7 @@ in ``em``. .. rubric:: Attach the Second Continuation -.. literalinclude:: connect.example.c +.. literalinclude:: communicate.example.c :lineno-match: :start-at: em = amongoc_then( :end-at: after_hello); @@ -184,7 +184,7 @@ The Second Continuation The second continuation after we receive a response from the server is very simple: -.. literalinclude:: connect.example.c +.. literalinclude:: communicate.example.c :lineno-match: :start-at: after_hello() :end-before: end. @@ -202,7 +202,7 @@ Going back to ``main()``, after our call to `amongoc_let` in which we attached the first continuation, we use `amongoc_tie` to convert the emitter to an `amongoc_operation`: -.. literalinclude:: connect.example.c +.. literalinclude:: communicate.example.c :lineno-match: :start-at: fin_status :end-at: amongoc_tie @@ -217,7 +217,7 @@ final result value will be (in a successful case, this would just be the Start the Operation, Run the Loop, and Clean Up ############################################### -.. literalinclude:: connect.example.c +.. literalinclude:: communicate.example.c :lineno-match: :start-at: amongoc_start :end-at: default_loop_destroy @@ -241,7 +241,7 @@ Finally, we are done with the event loop, and we can destroy it with Print the Final Result ###################### -.. literalinclude:: connect.example.c +.. literalinclude:: communicate.example.c :lineno-match: :start-at: is_error :end-before: end. diff --git a/docs/how-to/index.rst b/docs/how-to/index.rst index 8026f6c..6ea31bb 100644 --- a/docs/how-to/index.rst +++ b/docs/how-to/index.rst @@ -6,5 +6,5 @@ How-to Guides :caption: Guides :maxdepth: 2 - connect + communicate looping From 9083209576b12afc8de11e61113cc58d7fdaf4af Mon Sep 17 00:00:00 2001 From: vector-of-bool Date: Wed, 18 Dec 2024 15:12:04 -0700 Subject: [PATCH 08/14] detach_start() convenience API --- docs/ref/async.rst | 14 ++++++++++++++ include/amongoc/async.h | 6 ++++++ src/amongoc/async.cpp | 25 ++++++++++++++++++++++++- 3 files changed, 44 insertions(+), 1 deletion(-) diff --git a/docs/ref/async.rst b/docs/ref/async.rst index 4004a13..f89bdf6 100644 --- a/docs/ref/async.rst +++ b/docs/ref/async.rst @@ -290,6 +290,20 @@ Other This function is equivalent to :expr:`amongoc_tie(em, nullptr, nullptr, alloc)` +.. function:: + void amongoc_detach_start(amongoc_emitter [[transfer]] em) + + Launch the asynchronous operation defined by an emitter. + + :param em: |attr.transfer| An emitter that defines an asynchronous control + flow to be executed. + :allocation: The operation state is allocated using `mlib_default_allocator`. + + This will internally create an `amongoc_operation` state object, + `start it ` immediately, and destroy the operation state when + the operation completes. + + Types ##### diff --git a/include/amongoc/async.h b/include/amongoc/async.h index 9e9dd08..a24c044 100644 --- a/include/amongoc/async.h +++ b/include/amongoc/async.h @@ -419,4 +419,10 @@ amongoc_operation amongoc_tie(amongoc_emitter em, */ amongoc_operation amongoc_detach(amongoc_emitter emit, mlib_allocator alloc) mlib_noexcept; +/** + * @brief Launch an operation associated with an emitter. The associate operation state is + * deallocated automatically when the operation completes. + */ +void amongoc_detach_start(amongoc_emitter emit) mlib_noexcept; + mlib_extern_c_end(); diff --git a/src/amongoc/async.cpp b/src/amongoc/async.cpp index 74a99de..a537860 100644 --- a/src/amongoc/async.cpp +++ b/src/amongoc/async.cpp @@ -12,6 +12,8 @@ #include #include +#include + #include using namespace amongoc; @@ -106,7 +108,7 @@ amongoc_operation amongoc_tie(amongoc_emitter em, } } -amongoc_operation amongoc_detach(amongoc_emitter em, mlib_allocator alloc) mlib_noexcept { +amongoc_operation(amongoc_detach)(amongoc_emitter em, mlib_allocator alloc) mlib_noexcept { // Connect to a handler that simply discards the result values return amongoc_tie(em, nullptr, nullptr, alloc); } @@ -126,3 +128,24 @@ emitter amongoc_alloc_failure() noexcept { ret.vtable = &vtab; return ret; } + +void amongoc_detach_start(amongoc_emitter emit) mlib_noexcept { + auto em = std::move(emit).as_unique(); + struct consigned_operation { + // Dynamically allocated so it has a stable address + mlib::unique_ptr oper; + // Notify box that we are relocatable + using enable_trivially_relocatable [[maybe_unused]] = consigned_operation; + // The handler function, simply discarding the result + void operator()(emitter_result&&) const {} + // Take the allocator from the unique pointer + mlib::allocator<> get_allocator() const noexcept { return oper.get_deleter().alloc; } + }; + consigned_operation co{mlib::allocate_unique(::mlib_default_allocator)}; + // Take a stable reference to the operation state storage + auto& oper = *co.oper; + // Create the operation state and store it in the dynamic location + oper = std::move(em).connect(unique_handler::from(std::move(co))); + // Launch immediately + oper.start(); +} From d2669b3270d392a52e6f533b0c0604d4e3a895d1 Mon Sep 17 00:00:00 2001 From: vector-of-bool Date: Wed, 18 Dec 2024 15:12:28 -0700 Subject: [PATCH 09/14] Allow omitting alloc arg in detach() --- docs/ref/async.rst | 4 +++- include/amongoc/async.h | 4 ++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/ref/async.rst b/docs/ref/async.rst index f89bdf6..b9f518c 100644 --- a/docs/ref/async.rst +++ b/docs/ref/async.rst @@ -273,7 +273,9 @@ Other the returned `amongoc_operation` completes or is destroyed. -.. function:: amongoc_operation amongoc_detach(amongoc_emitter [[transfer]] em, mlib_allocator alloc) +.. function:: + amongoc_operation amongoc_detach(amongoc_emitter [[transfer]] em) + amongoc_operation amongoc_detach(amongoc_emitter [[transfer]] em, mlib_allocator alloc) Create a "detached" operation for an emitter. diff --git a/include/amongoc/async.h b/include/amongoc/async.h index a24c044..8602186 100644 --- a/include/amongoc/async.h +++ b/include/amongoc/async.h @@ -419,6 +419,10 @@ amongoc_operation amongoc_tie(amongoc_emitter em, */ amongoc_operation amongoc_detach(amongoc_emitter emit, mlib_allocator alloc) mlib_noexcept; +#define amongoc_detach(...) MLIB_ARGC_PICK(_amongocDetach, __VA_ARGS__) +#define _amongocDetach_argc_1(Operation) amongoc_detach(Operation, mlib_default_allocator) +#define _amongocDetach_argc_2 amongoc_detach + /** * @brief Launch an operation associated with an emitter. The associate operation state is * deallocated automatically when the operation completes. From 4415f13caa06749f77e06834b59adc8263e61853 Mon Sep 17 00:00:00 2001 From: vector-of-bool Date: Thu, 19 Dec 2024 11:19:46 -0700 Subject: [PATCH 10/14] Write a "connect" tutorial --- docs/learn/connect.example.c | 44 +++++++++++ docs/learn/connect.rst | 145 +++++++++++++++++++++++++++++++++++ docs/learn/index.rst | 1 + 3 files changed, 190 insertions(+) create mode 100644 docs/learn/connect.example.c create mode 100644 docs/learn/connect.rst diff --git a/docs/learn/connect.example.c b/docs/learn/connect.example.c new file mode 100644 index 0000000..1dd467b --- /dev/null +++ b/docs/learn/connect.example.c @@ -0,0 +1,44 @@ +#include // Make all APIs visible + +#include +#include +// end:headers + +amongoc_box on_connect(amongoc_box userdata, amongoc_status* status, amongoc_box result); + +int main(void) { + amongoc_loop loop; + amongoc_default_loop_init(&loop); + + // Initiate a connection + amongoc_emitter em = amongoc_client_new(&loop, "mongodb://localhost:27017"); + // Set the continuation + em = amongoc_then(em, &on_connect); + // Run the program + amongoc_detach_start(em); + amongoc_default_loop_run(&loop); + // Clean up + amongoc_default_loop_destroy(&loop); + return 0; +} + +// on_connect def +amongoc_box on_connect(amongoc_box userdata, amongoc_status* status, amongoc_box result) { + // We don't use the userdata + (void)userdata; + // Check for an error + if (amongoc_is_error(*status)) { + char* msg = amongoc_status_strdup_message(*status); + fprintf(stderr, "Error while connecting to server: %s\n", msg); + free(msg); + } else { + printf("Successfully connected!\n"); + amongoc_client* client; + amongoc_box_take(client, result); + // `cl` now stores a valid client. We don't do anything else, so just delete it: + amongoc_client_delete(client); + } + amongoc_box_destroy(result); + return amongoc_nil; +} +// on_connect end diff --git a/docs/learn/connect.rst b/docs/learn/connect.rst new file mode 100644 index 0000000..2ab374d --- /dev/null +++ b/docs/learn/connect.rst @@ -0,0 +1,145 @@ +############################## +Connecting to a MongoDB Server +############################## + +This page will show how to asynchronously connect to a MongoDB server from a C +program. + + +Including the |amongoc| APIs +############################ + +To make all |amongoc| APIs visible, include the everything-header: + +.. literalinclude:: connect.example.c + :caption: Headers + :start-at: #include + :end-before: end:headers + :lineno-match: + :dedent: + + +Creating an Event Loop +###################### + +The first thing to do before connecting to a program is to create an event loop. +|amongoc| includes a basic default single-threaded event loop suitable for basic +programs: + +.. literalinclude:: connect.example.c + :caption: Create a default event loop + :start-at: int main + :end-at: default_loop_init + :lineno-match: + :dedent: + + +Creating a Client +################# + +A client is created and initialized asynchronously and is associated with an +event loop, done using `amongoc_client_new`: + +.. literalinclude:: connect.example.c + :caption: Create a client emitter + :start-at: client_new + :end-at: ; + :lineno-match: + :dedent: + +.. hint:: The URI string above is a simple default for a locally-running server + without TLS enabled. You should replace it with your own if your URI is + running in a different location. + + +Create the Continuation +####################### + +An `amongoc_emitter` object is not a client. Rather, it is an object that +represents an asynchronous operation. To get the client, we need to attach a +continuation: + +.. literalinclude:: connect.example.c + :caption: Continuation prototype + :start-at: on_connect + :end-at: ; + :lineno-match: + :dedent: + +.. literalinclude:: connect.example.c + :caption: Attach the continuation with `amongoc_then` + :start-at: amongoc_then + :end-at: ; + :lineno-match: + :dedent: + +The continuation function ``on_connect`` looks like this: + +.. literalinclude:: connect.example.c + :caption: Continuation + :start-after: on_connect def + :end-before: on_connect end + :lineno-match: + :dedent: + + +Create the Operation State +########################## + +When we are done defining the entire asynchronous control flow, we need to +convert the `amongoc_emitter` to an operation and enqueue it with the event +loop. There are several ways to do this, but the simplest is `amongoc_detach_start`: + +.. literalinclude:: connect.example.c + :caption: Launch the operation + :start-at: detach_start + :end-at: detach_start + :lineno-match: + :dedent: + +This will enqueue the associated program with the event loop, and will +automatically release resources associated with the operation when the operation +completes. + +.. note:: `amongoc_detach_start` will "consume" the emitter object. The ``em`` + emitter object is "poisoned" and cannot be manipulated further. + + +Run the Program +############### + +The `amongoc_detach_start` call only enqueues the operation, but does not +execute it. We need to actually give control of the main thread to the event +loop. This is done using `amongoc_default_loop_run`: + +.. literalinclude:: connect.example.c + :caption: Run the program + :start-at: default_loop_run + :end-at: default_loop_run + :lineno-match: + :dedent: + + +Clean up the Event Loop +####################### + +After `amongoc_default_loop_run` returns, there is no more pending work in the +event loop, so we are done. Before returning, we need to destroy the event loop +object: + +.. literalinclude:: connect.example.c + :caption: Destroy the event loop + :start-at: default_loop_destroy + :end-at: default_loop_destroy + :lineno-match: + :dedent: + + +The Whole Program +################# + +Here is the complete program: + +.. literalinclude:: connect.example.c + :caption: ``connect.example.c`` + :lineno-match: diff --git a/docs/learn/index.rst b/docs/learn/index.rst index 63430bf..8b27d91 100644 --- a/docs/learn/index.rst +++ b/docs/learn/index.rst @@ -8,3 +8,4 @@ Tutorials bson/index box + connect From 7ab2a0cd6b39ebd8081aac5be4a64c1cf3848658 Mon Sep 17 00:00:00 2001 From: vector-of-bool Date: Thu, 19 Dec 2024 11:19:59 -0700 Subject: [PATCH 11/14] More doc words to highlight --- docs/conf.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 1cf6e0d..c51b41d 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -354,14 +354,16 @@ def merge_domaindata(self, docnames: list[str], otherdata: dict[str, Any]) -> No *( f"amongoc_{f}" for f in ( + "detach_start", + "detach", + "is_error", "just", "let", + "schedule_later", "start", - "tie", "then", + "tie", "timeout", - "schedule_later", - "is_error", ) ), *( From 920e43014005aabea0030f09a26307150f3acd37 Mon Sep 17 00:00:00 2001 From: vector-of-bool Date: Thu, 19 Dec 2024 11:20:52 -0700 Subject: [PATCH 12/14] Remove guideline: Never add ctors to C types --- docs/dev/guidelines.rst | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/docs/dev/guidelines.rst b/docs/dev/guidelines.rst index e2720b5..4703289 100644 --- a/docs/dev/guidelines.rst +++ b/docs/dev/guidelines.rst @@ -372,9 +372,7 @@ portion of the file. These should be simple wrappers around the C types (e.g. ============================================== This can create a semantic ambiguity when a C struct is constructed in a C -header. If you really need it, make sure that all calls to that constructor -within C headers are syntactically valid and semantically equivalent when -compiled in C and C++ modes (See: `amongoc_status`). +header. **Instead, prefer** to use the named-constructor idiom: Use |static| member functions that construct instances of the object (e.g. `amongoc_status::from`). From 4d5ff0e0f94e5e3584a8d7824b5427c3a4091b1a Mon Sep 17 00:00:00 2001 From: vector-of-bool Date: Thu, 19 Dec 2024 11:27:49 -0700 Subject: [PATCH 13/14] Support handlers from allocator-carrying funcs --- include/amongoc/handler.hpp | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/include/amongoc/handler.hpp b/include/amongoc/handler.hpp index d9b01e7..6bac9d6 100644 --- a/include/amongoc/handler.hpp +++ b/include/amongoc/handler.hpp @@ -3,6 +3,7 @@ #include #include #include +#include namespace amongoc { @@ -124,12 +125,25 @@ struct unique_handler : mlib::unique<::amongoc_handler> { "The from() invocable must be callable as fn(emitter_result&&)"); } + /** + * @brief Create a handler that invokes the given invocable when it completes. + * + * @param fn The invocable. Must have an associated allocator + */ + template + requires mlib::has_mlib_allocator + static unique_handler from(F&& fn) noexcept(box_inlinable_type) { + auto a = mlib::get_allocator(fn); + return from(a, mlib_fwd(fn)); + } + private: // Implement the wrapper for invocable objects, used by from() template struct wrapper { mlib::allocator<> _alloc; [[no_unique_address]] R _fn; + AMONGOC_TRIVIALLY_RELOCATABLE_THIS(amongoc::enable_trivially_relocatable_v, wrapper); static void _complete(amongoc_handler* self, status st, box result) noexcept { auto& fn = self->userdata.view.as()._fn; @@ -156,6 +170,7 @@ struct unique_handler : mlib::unique<::amongoc_handler> { : _fn(mlib_fwd(r)) {} [[no_unique_address]] R _fn; + AMONGOC_TRIVIALLY_RELOCATABLE_THIS(amongoc::enable_trivially_relocatable_v, wrapper); static void _complete(amongoc_handler* self, status st, box result) noexcept { auto& fn = self->userdata.view.as()._fn; From 1695ba28f19f03b559af21b3242e296558a9cf21 Mon Sep 17 00:00:00 2001 From: vector-of-bool Date: Thu, 9 Jan 2025 14:14:50 -0700 Subject: [PATCH 14/14] Don't enable message tracing by default --- src/amongoc/wire/proto.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/amongoc/wire/proto.hpp b/src/amongoc/wire/proto.hpp index ecbfb5d..0b939cd 100644 --- a/src/amongoc/wire/proto.hpp +++ b/src/amongoc/wire/proto.hpp @@ -31,7 +31,7 @@ namespace amongoc::wire { namespace trace { // Global toggle for enabling message tracing -constexpr bool enabled = true; +constexpr bool enabled = false; // Print information for a message header void message_header(std::string_view prefix,