#include "common-prelude.h"

#ifndef BSON_BSON_DSL_H_INCLUDED
#define BSON_BSON_DSL_H_INCLUDED

/**
 * @file bson-dsl.h
 * @brief Define a C-preprocessor DSL for working with BSON objects
 *
 * This file defines an embedded DSL for working with BSON objects consisely and
 * correctly.
 *
 * For more information about using this DSL, refer to `bson-dsl.md`.
 */

#include "bson/bson.h"

enum {
   /// Toggle this value to enable/disable debug output for all bsonDSL
   /// operations (printed to stderr). You can also set a constant
   /// BSON_DSL_DEBUG within the scope of a DSL command to selectively debug
   /// only the commands within that scope.
   BSON_DSL_DEBUG = 0
};

#define _bson_thread_local BSON_IF_GNU_LIKE (__thread) BSON_IF_MSVC (__declspec (thread))

#define _bson_comdat                        \
   BSON_IF_WINDOWS (__declspec (selectany)) \
   BSON_IF_POSIX (__attribute__ ((weak)))

#ifdef __GNUC__
// GCC has a bug handling pragma statements that disable warnings within complex
// nested macro expansions. If we're GCC, just disable -Wshadow outright:
BSON_IF_GNU_LIKE (_Pragma ("GCC diagnostic ignored \"-Wshadow\""))
#endif

#define _bsonDSL_disableWarnings()                                        \
   if (1) {                                                               \
      BSON_IF_GNU_LIKE (_Pragma ("GCC diagnostic push");)                 \
      BSON_IF_GNU_LIKE (_Pragma ("GCC diagnostic ignored \"-Wshadow\"");) \
   } else                                                                 \
      ((void) 0)

#define _bsonDSL_restoreWarnings()                       \
   if (1) {                                              \
      BSON_IF_GNU_LIKE (_Pragma ("GCC diagnostic pop");) \
   } else                                                \
      ((void) 0)

/**
 * @brief Parse the given BSON document.
 *
 * @param doc A bson_t object to walk. (Not a pointer)
 */
#define bsonParse(Document, ...)                              \
   _bsonDSL_begin ("bsonParse(%s)", _bsonDSL_str (Document)); \
   _bsonDSL_disableWarnings ();                               \
   bsonParseError = NULL;                                     \
   BSON_MAYBE_UNUSED bool _bvHalt = false;                    \
   BSON_MAYBE_UNUSED const bool _bvContinue = false;          \
   BSON_MAYBE_UNUSED const bool _bvBreak = false;             \
   _bsonDSL_eval (_bsonParse ((Document), __VA_ARGS__));      \
   _bsonDSL_restoreWarnings ();                               \
   _bsonDSL_end

/**
 * @brief Visit each element of a BSON document
 */
#define bsonVisitEach(Document, ...)                              \
   _bsonDSL_begin ("bsonVisitEach(%s)", _bsonDSL_str (Document)); \
   _bsonDSL_disableWarnings ();                                   \
   BSON_MAYBE_UNUSED bool _bvHalt = false;                        \
   _bsonDSL_eval (_bsonVisitEach ((Document), __VA_ARGS__));      \
   _bsonDSL_restoreWarnings ();                                   \
   _bsonDSL_end

#define bsonBuildContext (*_bsonBuildContextThreadLocalPtr)
#define bsonVisitContext (*_bsonVisitContextThreadLocalPtr)
#define bsonVisitIter (bsonVisitContext.iter)

/// Begin any function-like macro by opening a new scope and writing a debug
/// message.
#define _bsonDSL_begin(Str, ...)        \
   if (true) {                          \
      _bsonDSLDebug (Str, __VA_ARGS__); \
   ++_bson_dsl_indent

/// End a function-like macro scope.
#define _bsonDSL_end   \
   --_bson_dsl_indent; \
   }                   \
   else ((void) 0)

/**
 * @brief Expands to a call to bson_append_{Kind}, with the three first
 * arguments filled in by the DSL context variables.
 */
#define _bsonBuildAppendArgs bsonBuildContext.doc, bsonBuildContext.key, bsonBuildContext.key_len

/**
 * The _bsonDocOperation_XYZ macros handle the top-level bsonBuild()
 * items, and any nested doc() items, with XYZ being the doc-building
 * subcommand.
 */
#define _bsonDocOperation(Command, _ignore, _count)                                    \
   if (!bsonBuildError) {                                                              \
      _bsonDocOperation_##Command;                                                     \
      if (bsonBuildError) {                                                            \
         _bsonDSLDebug ("Stopping doc() due to bsonBuildError: [%s]", bsonBuildError); \
      }                                                                                \
   }

#define _bsonValueOperation(P) _bsonValueOperation_##P

/// key-value pair with explicit key length
#define _bsonDocOperation_kvl(String, Len, Element)                            \
   _bsonDSL_begin ("\"%s\" => [%s]", String, _bsonDSL_strElide (30, Element)); \
   const char *_bbString = (String);                                           \
   const uint64_t length = (Len);                                              \
   if (bson_in_range_unsigned (int, length)) {                                 \
      _bbCtx.key = _bbString;                                                  \
      _bbCtx.key_len = (int) length;                                           \
      _bsonValueOperation (Element);                                           \
   } else {                                                                    \
      bsonBuildError = "Out-of-range key string length value";                 \
   }                                                                           \
   _bsonDSL_end

/// Key-value pair with a C-string
#define _bsonDocOperation_kv(String, Element) _bsonDocOperation_kvl ((String), strlen ((String)), Element)

/// Execute arbitrary code
#define _bsonDocOperation_do(...)                                      \
   _bsonDSL_begin ("do(%s)", _bsonDSL_strElide (30, __VA_ARGS__));     \
   do {                                                                \
      __VA_ARGS__;                                                     \
   } while (0);                                                        \
   if (bsonBuildError) {                                               \
      _bsonDSLDebug ("do() set bsonBuildError: [%s]", bsonBuildError); \
   }                                                                   \
   _bsonDSL_end

/// We must defer expansion of the nested doc() to allow "recursive" evaluation
#define _bsonValueOperation_doc _bsonValueOperationDeferred_doc _bsonDSL_nothing ()
#define _bsonArrayOperation_doc(...) _bsonArrayAppendValue (doc (__VA_ARGS__))

#define _bsonValueOperationDeferred_doc(...)                                                   \
   _bsonDSL_begin ("doc(%s)", _bsonDSL_strElide (30, __VA_ARGS__));                            \
   /* Write to this variable as the child: */                                                  \
   bson_t _bbChildDoc = BSON_INITIALIZER;                                                      \
   if (!bson_append_document_begin (_bsonBuildAppendArgs, &_bbChildDoc)) {                     \
      bsonBuildError = "Error while initializing child document: " _bsonDSL_str (__VA_ARGS__); \
   } else {                                                                                    \
      _bsonBuildAppend (_bbChildDoc, __VA_ARGS__);                                             \
      if (!bsonBuildError) {                                                                   \
         if (!bson_append_document_end (bsonBuildContext.doc, &_bbChildDoc)) {                 \
            bsonBuildError = "Error while finalizing document: " _bsonDSL_str (__VA_ARGS__);   \
         }                                                                                     \
      }                                                                                        \
   }                                                                                           \
   _bsonDSL_end

/// We must defer expansion of the nested array() to allow "recursive"
/// evaluation
#define _bsonValueOperation_array _bsonValueOperationDeferred_array _bsonDSL_nothing ()
#define _bsonArrayOperation_array(...) _bsonArrayAppendValue (array (__VA_ARGS__))

#define _bsonValueOperationDeferred_array(...)                                                  \
   _bsonDSL_begin ("array(%s)", _bsonDSL_strElide (30, __VA_ARGS__));                           \
   /* Write to this variable as the child array: */                                             \
   bson_t _bbArray = BSON_INITIALIZER;                                                          \
   if (!bson_append_array_begin (_bsonBuildAppendArgs, &_bbArray)) {                            \
      bsonBuildError = "Error while initializing child array: " _bsonDSL_str (__VA_ARGS__);     \
   } else {                                                                                     \
      _bsonBuildArray (_bbArray, __VA_ARGS__);                                                  \
      if (!bsonBuildError) {                                                                    \
         if (!bson_append_array_end (bsonBuildContext.doc, &_bbArray)) {                        \
            bsonBuildError = "Error while finalizing child array: " _bsonDSL_str (__VA_ARGS__); \
         }                                                                                      \
      } else {                                                                                  \
         _bsonDSLDebug ("Got bsonBuildError: [%s]", bsonBuildError);                            \
      }                                                                                         \
   }                                                                                            \
   _bsonDSL_end

/// Append a UTF-8 string with an explicit length
#define _bsonValueOperation_utf8_w_len(String, Len)                                 \
   if (!bson_append_utf8 (_bsonBuildAppendArgs, (String), (int) (Len))) {           \
      bsonBuildError = "Error while appending utf8 string: " _bsonDSL_str (String); \
   } else                                                                           \
      ((void) 0)
#define _bsonArrayOperation_utf8_w_len(X) _bsonArrayAppendValue (utf8_w_len (X))

/// Append a "cstr" as UTF-8
#define _bsonValueOperation_cstr(String) _bsonValueOperation_utf8_w_len ((String), strlen (String))
#define _bsonArrayOperation_cstr(X) _bsonArrayAppendValue (cstr (X))

/// Append an int32
#define _bsonValueOperation_int32(Integer)                                        \
   if (!bson_append_int32 (_bsonBuildAppendArgs, (Integer))) {                    \
      bsonBuildError = "Error while appending int32(" _bsonDSL_str (Integer) ")"; \
   } else                                                                         \
      ((void) 0)
#define _bsonArrayOperation_int32(X) _bsonArrayAppendValue (int32 (X))

/// Append an int64
#define _bsonValueOperation_int64(Integer)                                        \
   if (!bson_append_int64 (_bsonBuildAppendArgs, (Integer))) {                    \
      bsonBuildError = "Error while appending int64(" _bsonDSL_str (Integer) ")"; \
   } else                                                                         \
      ((void) 0)
#define _bsonArrayOperation_int64(X) _bsonArrayAppendValue (int64 (X))

/// Append the value referenced by a given iterator
#define _bsonValueOperation_iterValue(Iter)                                        \
   if (!bson_append_iter (_bsonBuildAppendArgs, &(Iter))) {                        \
      bsonBuildError = "Error while appending iterValue(" _bsonDSL_str (Iter) ")"; \
   } else                                                                          \
      ((void) 0)
#define _bsonArrayOperation_iterValue(X) _bsonArrayAppendValue (iterValue (X))

/// Append the BSON document referenced by the given pointer
#define _bsonValueOperation_bson(Doc)                                                     \
   if (!bson_append_document (_bsonBuildAppendArgs, &(Doc))) {                            \
      bsonBuildError = "Error while appending subdocument: bson(" _bsonDSL_str (Doc) ")"; \
   } else                                                                                 \
      ((void) 0)
#define _bsonArrayOperation_bson(X) _bsonArrayAppendValue (bson (X))

/// Append the BSON document referenced by the given pointer as an array
#define _bsonValueOperation_bsonArray(Arr)                         \
   if (!bson_append_array (_bsonBuildAppendArgs, &(Arr))) {        \
      bsonBuildError = "Error while appending subdocument array: " \
                       "bsonArray(" _bsonDSL_str (Arr) ")";        \
   } else                                                          \
      ((void) 0)
#define _bsonArrayOperation_bsonArray(X) _bsonArrayAppendValue (bsonArray (X))

#define _bsonValueOperation_bool(b)                                        \
   if (!bson_append_bool (_bsonBuildAppendArgs, (b))) {                    \
      bsonBuildError = "Error while appending bool(" _bsonDSL_str (b) ")"; \
   } else                                                                  \
      ((void) 0)
#define _bsonArrayOperation_bool(X) _bsonArrayAppendValue (bool (X))
#define _bsonValueOperation__Bool(b) _bsonValueOperation_bool (b)
#define _bsonArrayOperation__Bool(X) _bsonArrayAppendValue (_Bool (X))

#define _bsonValueOperation_null                       \
   if (!bson_append_null (_bsonBuildAppendArgs)) {     \
      bsonBuildError = "Error while appending a null"; \
   } else                                              \
      ((void) 0)
#define _bsonArrayOperation_null _bsonValueOperation (null)

#define _bsonArrayOperation_value(X) _bsonArrayAppendValue (value (X))

#define _bsonValueOperation_value(Value)                                        \
   _bsonDSL_begin ("value(%s)", _bsonDSL_str (Value));                          \
   if (!bson_append_value (_bsonBuildAppendArgs, &(Value))) {                   \
      bsonBuildError = "Error while appending value(" _bsonDSL_str (Value) ")"; \
   }                                                                            \
   _bsonDSL_end

/// Insert the given BSON document into the parent document in-place
#define _bsonDocOperation_insert(OtherBSON, Pred)                                                    \
   _bsonDSL_begin ("Insert other document: [%s]", _bsonDSL_str (OtherBSON));                         \
   const bool _bvHalt = false; /* Required for _bsonVisitEach() */                                   \
   _bsonVisitEach (OtherBSON, if (Pred, then (do (_bsonDocOperation_iterElement (bsonVisitIter))))); \
   _bsonDSL_end

#define _bsonDocOperation_insertFromIter(Iter, Pred)                               \
   _bsonDSL_begin ("Insert document from iterator: [%s]", _bsonDSL_str (Iter));    \
   bson_t _bbDocFromIter = _bson_dsl_iter_as_doc (&(Iter));                        \
   if (_bbDocFromIter.len == 0) {                                                  \
      _bsonDSLDebug ("NOTE: Skipping insert of non-document value from iterator"); \
   } else {                                                                        \
      _bsonDocOperation_insert (_bbDocFromIter, Pred);                             \
   }                                                                               \
   _bsonDSL_end

#define _bsonDocOperation_iterElement(Iter)                                                             \
   _bsonDSL_begin ("Insert element from bson_iter_t [%s]", _bsonDSL_str (Iter));                        \
   bson_iter_t _bbIter = (Iter);                                                                        \
   _bsonDocOperation_kvl (bson_iter_key (&_bbIter), bson_iter_key_len (&_bbIter), iterValue (_bbIter)); \
   _bsonDSL_end

/// Insert the given BSON document into the parent array. Keys of the given
/// document are discarded and it is treated as an array of values.
#define _bsonArrayOperation_insert(OtherArr, Pred)                                                  \
   _bsonDSL_begin ("Insert other array: [%s]", _bsonDSL_str (OtherArr));                            \
   _bsonVisitEach (OtherArr, if (Pred, then (do (_bsonArrayOperation_iterValue (bsonVisitIter))))); \
   _bsonDSL_end

#define _bsonArrayAppendValue(ValueOperation)                                                             \
   _bsonDSL_begin ("[%d] => [%s]", (int) bsonBuildContext.index, _bsonDSL_strElide (30, ValueOperation)); \
   /* Set the doc key to the array index as a string: */                                                  \
   _bsonBuild_setKeyToArrayIndex (bsonBuildContext.index);                                                \
   /* Append a value: */                                                                                  \
   _bsonValueOperation_##ValueOperation;                                                                  \
   /* Increment the array index: */                                                                       \
   ++_bbCtx.index;                                                                                        \
   _bsonDSL_end


#define _bsonDocOperationIfThen_then _bsonBuildAppendWithCurrentContext
#define _bsonDocOperationIfElse_else _bsonBuildAppendWithCurrentContext

#define _bsonDocOperationIfThenElse(Condition, Then, Else)              \
   if ((Condition)) {                                                   \
      _bsonDSLDebug ("Taking TRUE branch: [%s]", _bsonDSL_str (Then));  \
      _bsonDocOperationIfThen_##Then;                                   \
   } else {                                                             \
      _bsonDSLDebug ("Taking FALSE branch: [%s]", _bsonDSL_str (Else)); \
      _bsonDocOperationIfElse_##Else;                                   \
   }

#define _bsonDocOperationIfThen(Condition, Then)                       \
   if ((Condition)) {                                                  \
      _bsonDSLDebug ("Taking TRUE branch: [%s]", _bsonDSL_str (Then)); \
      _bsonDocOperationIfThen_##Then;                                  \
   }

#define _bsonDocOperation_if(Condition, ...)                                                                 \
   _bsonDSL_begin ("Conditional append on [%s]", _bsonDSL_str (Condition));                                  \
   /* Pick a sub-macro depending on if there are one or two args */                                          \
   _bsonDSL_ifElse (_bsonDSL_hasComma (__VA_ARGS__), _bsonDocOperationIfThenElse, _bsonDocOperationIfThen) ( \
      Condition, __VA_ARGS__);                                                                               \
   _bsonDSL_end

#define _bsonArrayOperationIfThen_then _bsonBuildArrayWithCurrentContext
#define _bsonArrayOperationIfElse_else _bsonBuildArrayWithCurrentContext

#define _bsonArrayOperationIfThenElse(Condition, Then, Else)            \
   if ((Condition)) {                                                   \
      _bsonDSLDebug ("Taking TRUE branch: [%s]", _bsonDSL_str (Then));  \
      _bsonArrayOperationIfThen_##Then;                                 \
   } else {                                                             \
      _bsonDSLDebug ("Taking FALSE branch: [%s]", _bsonDSL_str (Else)); \
      _bsonArrayOperationIfElse_##Else;                                 \
   }

#define _bsonArrayOperationIfThen(Condition, Then)                     \
   if ((Condition)) {                                                  \
      _bsonDSLDebug ("Taking TRUE branch: [%s]", _bsonDSL_str (Then)); \
      _bsonArrayOperationIfThen_##Then;                                \
   }

#define _bsonArrayOperation_if(Condition, ...)                                                                   \
   _bsonDSL_begin ("Conditional value on [%s]", _bsonDSL_str (Condition));                                       \
   /* Pick a sub-macro depending on if there are one or two args */                                              \
   _bsonDSL_ifElse (_bsonDSL_hasComma (__VA_ARGS__), _bsonArrayOperationIfThenElse, _bsonArrayOperationIfThen) ( \
      Condition, __VA_ARGS__);                                                                                   \
   _bsonDSL_end

#define _bsonValueOperationIf_then(X) _bsonValueOperation_##X
#define _bsonValueOperationIf_else(X) _bsonValueOperation_##X

#define _bsonValueOperation_if(Condition, Then, Else)                   \
   if ((Condition)) {                                                   \
      _bsonDSLDebug ("Taking TRUE branch: [%s]", _bsonDSL_str (Then));  \
      _bsonValueOperationIf_##Then;                                     \
   } else {                                                             \
      _bsonDSLDebug ("Taking FALSE branch: [%s]", _bsonDSL_str (Else)); \
      _bsonValueOperationIf_##Else;                                     \
   }

#define _bsonBuild_setKeyToArrayIndex(Idx)                                                                       \
   _bbCtx.key_len = bson_snprintf (_bbCtx.index_key_str, sizeof _bbCtx.index_key_str, "%d", (int) _bbCtx.index); \
   _bbCtx.key = _bbCtx.index_key_str

/// Handle an element of array()
#define _bsonArrayOperation(Element, _nil, _count) \
   if (!bsonBuildError) {                          \
      _bsonArrayOperation_##Element;               \
   }

#define _bsonBuildAppendWithCurrentContext(...) _bsonDSL_mapMacro (_bsonDocOperation, ~, __VA_ARGS__)

#define _bsonBuildArrayWithCurrentContext(...) _bsonDSL_mapMacro (_bsonArrayOperation, ~, __VA_ARGS__)

#define _bsonDSL_Type_double BSON_TYPE_DOUBLE
#define _bsonDSL_Type_utf8 BSON_TYPE_UTF8
#define _bsonDSL_Type_doc BSON_TYPE_DOCUMENT
#define _bsonDSL_Type_array BSON_TYPE_ARRAY
#define _bsonDSL_Type_binary BSON_TYPE_BINARY
#define _bsonDSL_Type_undefined BSON_TYPE_UNDEFINED
#define _bsonDSL_Type_oid BSON_TYPE_OID
#define _bsonDSL_Type_bool BSON_TYPE_BOOL
// ("bool" may be spelled _Bool due to macro expansion:)
#define _bsonDSL_Type__Bool BSON_TYPE_BOOL
#define _bsonDSL_Type_date_time BSON_TYPE_DATE_TIME
#define _bsonDSL_Type_null BSON_TYPE_NULL
#define _bsonDSL_Type_regex BSON_TYPE_REGEX
#define _bsonDSL_Type_dbpointer BSON_TYPE_DBPOINTER
#define _bsonDSL_Type_code BSON_TYPE_CODE
#define _bsonDSL_Type_codewscope BSON_TYPE_CODEWSCOPE
#define _bsonDSL_Type_int32 BSON_TYPE_INT32
#define _bsonDSL_Type_timestamp BSON_TYPE_TIMESTAMP
#define _bsonDSL_Type_int64 BSON_TYPE_INT64
#define _bsonDSL_Type_decimal128 BSON_TYPE_DECIMAL128

#define _bsonDSL_Type_string __NOTE__No_type_named__string__did_you_mean__utf8

#define _bsonVisitOperation_halt _bvHalt = true

#define _bsonVisitOperation_if(Predicate, ...)                                                                \
   _bsonDSL_begin ("if(%s)", _bsonDSL_str (Predicate));                                                       \
   _bsonDSL_ifElse (_bsonDSL_hasComma (__VA_ARGS__), _bsonVisit_ifThenElse, _bsonVisit_ifThen) (Predicate,    \
                                                                                                __VA_ARGS__); \
   _bsonDSL_end

#define _bsonVisit_ifThenElse(Predicate, Then, Else) \
   if (bsonPredicate (Predicate)) {                  \
      _bsonDSLDebug ("then:");                       \
      _bsonVisit_ifThen_##Then;                      \
   } else {                                          \
      _bsonDSLDebug ("else:");                       \
      _bsonVisit_ifElse_##Else;                      \
   }

#define _bsonVisit_ifThen(Predicate, Then) \
   if (bsonPredicate (Predicate)) {        \
      _bsonDSLDebug ("then:");             \
      _bsonVisit_ifThen_##Then;            \
   } else {                                \
      _bsonDSLDebug ("[else nothing]");    \
   }

#define _bsonVisit_ifThen_then _bsonVisit_applyOps
#define _bsonVisit_ifElse_else _bsonVisit_applyOps

#define _bsonVisitOperation_storeBool(Dest)               \
   _bsonDSL_begin ("storeBool(%s)", _bsonDSL_str (Dest)); \
   (Dest) = bson_iter_as_bool (&bsonVisitIter);           \
   _bsonDSL_end

#define _bsonVisitOperation_storeStrRef(Dest)               \
   _bsonDSL_begin ("storeStrRef(%s)", _bsonDSL_str (Dest)); \
   (Dest) = bson_iter_utf8 (&bsonVisitIter, NULL);          \
   _bsonDSL_end

#define _bsonVisitOperation_storeStrDup(Dest)               \
   _bsonDSL_begin ("storeStrDup(%s)", _bsonDSL_str (Dest)); \
   (Dest) = bson_iter_dup_utf8 (&bsonVisitIter, NULL);      \
   _bsonDSL_end

#define _bsonVisitOperation_storeDocDup(Dest)               \
   _bsonDSL_begin ("storeDocDup(%s)", _bsonDSL_str (Dest)); \
   bson_t _bvDoc = BSON_INITIALIZER;                        \
   _bson_dsl_iter_as_doc (&_bvDoc, &bsonVisitIter);         \
   if (_bvDoc.len) {                                        \
      bson_copy_to (&_bvDoc, &(Dest));                      \
   }                                                        \
   _bsonDSL_end

#define _bsonVisitOperation_storeDocRef(Dest)               \
   _bsonDSL_begin ("storeDocRef(%s)", _bsonDSL_str (Dest)); \
   _bson_dsl_iter_as_doc (&(Dest), &bsonVisitIter);         \
   _bsonDSL_end

#define _bsonVisitOperation_storeDocDupPtr(Dest)               \
   _bsonDSL_begin ("storeDocDupPtr(%s)", _bsonDSL_str (Dest)); \
   bson_t _bvDoc = BSON_INITIALIZER;                           \
   _bson_dsl_iter_as_doc (&_bvDoc, &bsonVisitIter);            \
   if (_bvDoc.len) {                                           \
      (Dest) = bson_copy (&_bvDoc);                            \
   }                                                           \
   _bsonDSL_end

#define _bsonVisitOperation_storeInt32(Dest)               \
   _bsonDSL_begin ("storeInt32(%s)", _bsonDSL_str (Dest)); \
   (Dest) = bson_iter_int32 (&bsonVisitIter);              \
   _bsonDSL_end

#define _bsonVisitOperation_do(...)                                \
   _bsonDSL_begin ("do: %s", _bsonDSL_strElide (30, __VA_ARGS__)); \
   do {                                                            \
      __VA_ARGS__;                                                 \
   } while (0);                                                    \
   _bsonDSL_end

#define _bsonVisitOperation_appendTo(BSON)                                                                      \
   _bsonDSL_begin ("appendTo(%s)", _bsonDSL_str (BSON));                                                        \
   if (!bson_append_iter (                                                                                      \
          &(BSON), bson_iter_key (&bsonVisitIter), (int) bson_iter_key_len (&bsonVisitIter), &bsonVisitIter)) { \
      bsonParseError = "Error in appendTo(" _bsonDSL_str (BSON) ")";                                            \
   }                                                                                                            \
   _bsonDSL_end

#define _bsonVisitCase_when(Pred, ...)                 \
   _bsonDSL_begin ("when: [%s]", _bsonDSL_str (Pred)); \
   _bvCaseMatched = _bsonPredicate (Pred);             \
   if (_bvCaseMatched) {                               \
      _bsonVisit_applyOps (__VA_ARGS__);               \
   }                                                   \
   _bsonDSL_end

#define _bsonVisitCase_else(...)      \
   _bsonDSL_begin ("else:%s", "");    \
   _bvCaseMatched = true;             \
   _bsonVisit_applyOps (__VA_ARGS__); \
   _bsonDSL_end

#define _bsonVisitCase(Pair, _nil, _count) \
   if (!_bvCaseMatched) {                  \
      _bsonVisitCase_##Pair;               \
   } else                                  \
      ((void) 0);

#define _bsonVisitOperation_case(...)                  \
   _bsonDSL_begin ("case:%s", "");                     \
   BSON_MAYBE_UNUSED bool _bvCaseMatched = false;      \
   _bsonDSL_mapMacro (_bsonVisitCase, ~, __VA_ARGS__); \
   _bsonDSL_end

#define _bsonVisitOperation_append _bsonVisitOneApplyDeferred_append _bsonDSL_nothing ()
#define _bsonVisitOneApplyDeferred_append(Doc, ...)                                                 \
   _bsonDSL_begin ("append to [%s] : %s", _bsonDSL_str (Doc), _bsonDSL_strElide (30, __VA_ARGS__)); \
   _bsonBuildAppend (Doc, __VA_ARGS__);                                                             \
   if (bsonBuildError) {                                                                            \
      bsonParseError = bsonBuildError;                                                              \
   }                                                                                                \
   _bsonDSL_end

#define _bsonVisitEach(Doc, ...)                                                          \
   _bsonDSL_begin ("visitEach(%s)", _bsonDSL_str (Doc));                                  \
   do {                                                                                   \
      /* Reset the context */                                                             \
      struct _bsonVisitContext_t _bvCtx = {                                               \
         .doc = &(Doc),                                                                   \
         .parent = _bsonVisitContextThreadLocalPtr,                                       \
         .index = 0,                                                                      \
      };                                                                                  \
      _bsonVisitContextThreadLocalPtr = &_bvCtx;                                          \
      bsonParseError = NULL;                                                              \
      /* Iterate over each element of the document */                                     \
      if (!bson_iter_init (&_bvCtx.iter, &(Doc))) {                                       \
         bsonParseError = "Invalid BSON data [a]";                                        \
      }                                                                                   \
      BSON_MAYBE_UNUSED bool _bvBreak = false;                                            \
      BSON_MAYBE_UNUSED bool _bvContinue = false;                                         \
      while (bson_iter_next (&_bvCtx.iter) && !_bvHalt && !bsonParseError && !_bvBreak) { \
         _bvContinue = false;                                                             \
         _bsonVisit_applyOps (__VA_ARGS__);                                               \
         ++_bvCtx.index;                                                                  \
      }                                                                                   \
      if (bsonVisitIter.err_off) {                                                        \
         bsonParseError = "Invalid BSON data [b]";                                        \
      }                                                                                   \
      /* Restore the dsl context */                                                       \
      _bsonVisitContextThreadLocalPtr = _bvCtx.parent;                                    \
   } while (0);                                                                           \
   _bsonDSL_end

#define _bsonVisitOperation_visitEach _bsonVisitOperation_visitEachDeferred _bsonDSL_nothing ()
#define _bsonVisitOperation_visitEachDeferred(...)                             \
   _bsonDSL_begin ("visitEach:%s", "");                                        \
   do {                                                                        \
      const uint8_t *data;                                                     \
      uint32_t len;                                                            \
      bson_type_t typ = bson_iter_type_unsafe (&bsonVisitIter);                \
      if (typ == BSON_TYPE_ARRAY)                                              \
         bson_iter_array (&bsonVisitIter, &len, &data);                        \
      else if (typ == BSON_TYPE_DOCUMENT)                                      \
         bson_iter_document (&bsonVisitIter, &len, &data);                     \
      else {                                                                   \
         _bsonDSLDebug ("(Skipping visitEach() of non-array/document value)"); \
         break;                                                                \
      }                                                                        \
      bson_t inner;                                                            \
      BSON_ASSERT (bson_init_static (&inner, data, len));                      \
      _bsonVisitEach (inner, __VA_ARGS__);                                     \
   } while (0);                                                                \
   _bsonDSL_end

#define _bsonVisitOperation_nop _bsonDSLDebug ("[nop]")
#define _bsonVisitOperation_parse(...)                                    \
   do {                                                                   \
      const uint8_t *data;                                                \
      uint32_t len;                                                       \
      bson_type_t typ = bson_iter_type (&bsonVisitIter);                  \
      if (typ == BSON_TYPE_ARRAY)                                         \
         bson_iter_array (&bsonVisitIter, &len, &data);                   \
      else if (typ == BSON_TYPE_DOCUMENT)                                 \
         bson_iter_document (&bsonVisitIter, &len, &data);                \
      else {                                                              \
         _bsonDSLDebug ("Ignoring parse() for non-document/array value"); \
         break;                                                           \
      }                                                                   \
      bson_t inner;                                                       \
      BSON_ASSERT (bson_init_static (&inner, data, len));                 \
      _bsonParse (inner, __VA_ARGS__);                                    \
   } while (0);

#define _bsonVisitOperation_continue _bvContinue = true
#define _bsonVisitOperation_break _bvBreak = _bvContinue = true
#define _bsonVisitOperation_require(Predicate)                                  \
   _bsonDSL_begin ("require(%s)", _bsonDSL_str (Predicate));                    \
   if (!bsonPredicate (Predicate)) {                                            \
      bsonParseError = "Element requirement failed: " _bsonDSL_str (Predicate); \
   }                                                                            \
   _bsonDSL_end

#define _bsonVisitOperation_error(S) bsonParseError = (S)
#define _bsonVisitOperation_errorf(S, ...) (bsonParseError = _bson_dsl_errorf (&(S), __VA_ARGS__))
#define _bsonVisitOperation_dupPath(S)               \
   _bsonDSL_begin ("dupPath(%s)", _bsonDSL_str (S)); \
   _bson_dsl_dupPath (&(S));                         \
   _bsonDSL_end

#define _bsonVisit_applyOp(P, _const, _count)            \
   do {                                                  \
      if (!_bvContinue && !_bvHalt && !bsonParseError) { \
         _bsonVisitOperation_##P;                        \
      }                                                  \
   } while (0);

#define _bsonParse(Doc, ...)                                                                        \
   do {                                                                                             \
      BSON_MAYBE_UNUSED const bson_t *_bpDoc = &(Doc);                                              \
      /* Keep track of which elements have been visited based on their index*/                      \
      uint64_t _bpVisitBits_static[4] = {0};                                                        \
      BSON_MAYBE_UNUSED uint64_t *_bpVisitBits = _bpVisitBits_static;                               \
      BSON_MAYBE_UNUSED size_t _bpNumVisitBitInts = sizeof _bpVisitBits_static / sizeof (uint64_t); \
      BSON_MAYBE_UNUSED bool _bpFoundElement = false;                                               \
      _bsonParse_applyOps (__VA_ARGS__);                                                            \
      /* We may have allocated for visit bits */                                                    \
      if (_bpVisitBits != _bpVisitBits_static) {                                                    \
         bson_free (_bpVisitBits);                                                                  \
      }                                                                                             \
   } while (0)

#define _bsonParse_applyOps(...) _bsonDSL_mapMacro (_bsonParse_applyOp, ~, __VA_ARGS__)

/// Parse one entry referrenced by the context iterator
#define _bsonParse_applyOp(P, _nil, Counter) \
   do {                                      \
      if (!_bvHalt && !bsonParseError) {     \
         _bsonParseOperation_##P;            \
      }                                      \
   } while (0);

#define _bsonParseMarkVisited(Index)                                                               \
   if (1) {                                                                                        \
      const size_t nth_int = Index / 64u;                                                          \
      const size_t nth_bit = Index % 64u;                                                          \
      while (nth_int >= _bpNumVisitBitInts) {                                                      \
         /* Say that five times, fast: */                                                          \
         size_t new_num_visit_bit_ints = _bpNumVisitBitInts * 2u;                                  \
         uint64_t *new_visit_bit_ints = bson_malloc0 (sizeof (uint64_t) * new_num_visit_bit_ints); \
         memcpy (new_visit_bit_ints, _bpVisitBits, sizeof (uint64_t) * _bpNumVisitBitInts);        \
         if (_bpVisitBits != _bpVisitBits_static) {                                                \
            bson_free (_bpVisitBits);                                                              \
         }                                                                                         \
         _bpVisitBits = new_visit_bit_ints;                                                        \
         _bpNumVisitBitInts = new_num_visit_bit_ints;                                              \
      }                                                                                            \
                                                                                                   \
      _bpVisitBits[nth_int] |= (UINT64_C (1) << nth_bit);                                          \
   } else                                                                                          \
      ((void) 0)

#define _bsonParseDidVisitNth(Index) _bsonParseDidVisitNth_1 (Index / 64u, Index % 64u)
#define _bsonParseDidVisitNth_1(NthInt, NthBit) \
   (NthInt < _bpNumVisitBitInts && (_bpVisitBits[NthInt] & (UINT64_C (1) << NthBit)))

#define _bsonParseOperation_find(Predicate, ...)                                                                    \
   _bsonDSL_begin ("find(%s)", _bsonDSL_str (Predicate));                                                           \
   _bpFoundElement = false;                                                                                         \
   _bsonVisitEach (                                                                                                 \
      *_bpDoc,                                                                                                      \
      if (Predicate,                                                                                                \
          then (do (_bsonParseMarkVisited (bsonVisitContext.index); _bpFoundElement = true), __VA_ARGS__, break))); \
   if (!_bpFoundElement && !bsonParseError) {                                                                       \
      _bsonDSLDebug ("[not found]");                                                                                \
   }                                                                                                                \
   _bsonDSL_end

#define _bsonParseOperation_require(Predicate, ...)                                                                 \
   _bsonDSL_begin ("require(%s)", _bsonDSL_str (Predicate));                                                        \
   _bpFoundElement = false;                                                                                         \
   _bsonVisitEach (                                                                                                 \
      *_bpDoc,                                                                                                      \
      if (Predicate,                                                                                                \
          then (do (_bsonParseMarkVisited (bsonVisitContext.index); _bpFoundElement = true), __VA_ARGS__, break))); \
   if (!_bpFoundElement && !bsonParseError) {                                                                       \
      bsonParseError = "Failed to find a required element: " _bsonDSL_str (Predicate);                              \
   }                                                                                                                \
   _bsonDSL_end

#define _bsonParseOperation_visitOthers(...)                                                                      \
   _bsonDSL_begin ("visitOthers(%s)", _bsonDSL_strElide (30, __VA_ARGS__));                                       \
   _bsonVisitEach (*_bpDoc, if (not(eval (_bsonParseDidVisitNth (bsonVisitContext.index))), then (__VA_ARGS__))); \
   _bsonDSL_end

#define bsonPredicate(P) _bsonPredicate _bsonDSL_nothing () (P)
#define _bsonPredicate(P) _bsonPredicate_Condition_##P

#define _bsonPredicate_Condition_ __NOTE__Missing_name_for_a_predicate_expression

#define _bsonPredicate_Condition_allOf(...) (1 _bsonDSL_mapMacro (_bsonPredicateAnd, ~, __VA_ARGS__))
#define _bsonPredicate_Condition_anyOf(...) (0 _bsonDSL_mapMacro (_bsonPredicateOr, ~, __VA_ARGS__))
#define _bsonPredicate_Condition_not(...) (!(0 _bsonDSL_mapMacro (_bsonPredicateOr, ~, __VA_ARGS__)))
#define _bsonPredicateAnd(Pred, _ignore, _ignore1) &&_bsonPredicate _bsonDSL_nothing () (Pred)
#define _bsonPredicateOr(Pred, _ignore, _ignore2) || _bsonPredicate _bsonDSL_nothing () (Pred)

#define _bsonPredicate_Condition_eval(X) (X)

#define _bsonPredicate_Condition_key(...)                       \
   (_bson_dsl_key_is_anyof (bson_iter_key (&bsonVisitIter),     \
                            bson_iter_key_len (&bsonVisitIter), \
                            true /* case senstive */,           \
                            __VA_ARGS__,                        \
                            NULL))

#define _bsonPredicate_Condition_iKey(...)                      \
   (_bson_dsl_key_is_anyof (bson_iter_key (&bsonVisitIter),     \
                            bson_iter_key_len (&bsonVisitIter), \
                            false /* case insenstive */,        \
                            __VA_ARGS__,                        \
                            NULL))

#define _bsonPredicate_Condition_type(Type) (bson_iter_type (&bsonVisitIter) == _bsonDSL_Type_##Type)

#define _bsonPredicate_Condition_keyWithType(Key, Type) \
   (_bsonPredicate_Condition_allOf _bsonDSL_nothing () (key (Key), type (Type)))

#define _bsonPredicate_Condition_iKeyWithType(Key, Type) \
   (_bsonPredicate_Condition_allOf _bsonDSL_nothing () (iKey (Key), type (Type)))

#define _bsonPredicate_Condition_lastElement (_bson_dsl_iter_is_last_element (&bsonVisitIter))

#define _bsonPredicate_Condition_isNumeric BSON_ITER_HOLDS_NUMBER (&bsonVisitIter)

#define _bsonPredicate_Condition_1 1
#define _bsonPredicate_Condition_0 0
#define _bsonPredicate_Condition_true true
#define _bsonPredicate_Condition_false false

#define _bsonPredicate_Condition_isTrue (bson_iter_as_bool (&bsonVisitIter))
#define _bsonPredicate_Condition_isFalse (!bson_iter_as_bool (&bsonVisitIter))
#define _bsonPredicate_Condition_empty (_bson_dsl_is_empty_bson (&bsonVisitIter))

#define _bsonPredicate_Condition_strEqual(S) (_bson_dsl_test_strequal (S, true))
#define _bsonPredicate_Condition_iStrEqual(S) (_bson_dsl_test_strequal (S, false))

#define _bsonPredicate_Condition_eq(Type, Value) (_bsonPredicate_Condition_type (Type) && bsonAs (Type) == Value)

#define _bsonParseOperation_else _bsonParse_deferredElse _bsonDSL_nothing ()
#define _bsonParse_deferredElse(...)     \
   if (!_bpFoundElement) {               \
      _bsonDSL_begin ("else:%s", "");    \
      _bsonParse_applyOps (__VA_ARGS__); \
      _bsonDSL_end;                      \
   } else                                \
      ((void) 0)

#define _bsonParseOperation_do(...)                                \
   _bsonDSL_begin ("do: %s", _bsonDSL_strElide (30, __VA_ARGS__)); \
   do {                                                            \
      __VA_ARGS__;                                                 \
   } while (0);                                                    \
   _bsonDSL_end

#define _bsonParseOperation_halt _bvHalt = true

#define _bsonParseOperation_error(S) bsonParseError = (S)
#define _bsonParseOperation_errorf(S, ...) (bsonParseError = _bson_dsl_errorf (&(S), __VA_ARGS__))

/// Perform conditional parsing
#define _bsonParseOperation_if(Condition, ...)                                                                \
   _bsonDSL_begin ("if(%s)", _bsonDSL_str (Condition));                                                       \
   /* Pick a sub-macro depending on if there are one or two args */                                           \
   _bsonDSL_ifElse (_bsonDSL_hasComma (__VA_ARGS__), _bsonParse_ifThenElse, _bsonParse_ifThen) (Condition,    \
                                                                                                __VA_ARGS__); \
   _bsonDSL_end

#define _bsonParse_ifThen_then _bsonParse_applyOps
#define _bsonParse_ifElse_else _bsonParse_applyOps

#define _bsonParse_ifThenElse(Condition, Then, Else) \
   if ((Condition)) {                                \
      _bsonDSLDebug ("then:");                       \
      _bsonParse_ifThen_##Then;                      \
   } else {                                          \
      _bsonDSLDebug ("else:");                       \
      _bsonParse_ifElse_##Else;                      \
   }

#define _bsonParse_ifThen(Condition, Then)       \
   if ((Condition)) {                            \
      _bsonDSLDebug ("%s", _bsonDSL_str (Then)); \
      _bsonParse_ifThen_##Then;                  \
   } else {                                      \
      _bsonDSLDebug ("[else nothing]");          \
   }

#define _bsonParseOperation_append _bsonParseOperationDeferred_append _bsonDSL_nothing ()
#define _bsonParseOperationDeferred_append(Doc, ...)                                                \
   _bsonDSL_begin ("append to [%s] : %s", _bsonDSL_str (Doc), _bsonDSL_strElide (30, __VA_ARGS__)); \
   _bsonBuildAppend (Doc, __VA_ARGS__);                                                             \
   if (bsonBuildError) {                                                                            \
      bsonParseError = bsonBuildError;                                                              \
   }                                                                                                \
   _bsonDSL_end

#define _bsonVisit_applyOps _bsonVisit_applyOpsDeferred _bsonDSL_nothing ()
#define _bsonVisit_applyOpsDeferred(...)                      \
   do {                                                       \
      _bsonDSL_mapMacro (_bsonVisit_applyOp, ~, __VA_ARGS__); \
   } while (0);

#define bsonBuildArray(BSON, ...)                                                                       \
   _bsonDSL_begin ("bsonBuildArray(%s, %s)", _bsonDSL_str (BSON), _bsonDSL_strElide (30, __VA_ARGS__)); \
   _bsonDSL_eval (_bsonBuildArray (BSON, __VA_ARGS__));                                                 \
   _bsonDSL_end

#define _bsonBuildArray(BSON, ...)                     \
   do {                                                \
      _bsonDSL_disableWarnings ();                     \
      struct _bsonBuildContext_t _bbCtx = {            \
         .doc = &(BSON),                               \
         .parent = _bsonBuildContextThreadLocalPtr,    \
         .index = 0,                                   \
      };                                               \
      _bsonBuildContextThreadLocalPtr = &_bbCtx;       \
      _bsonBuildArrayWithCurrentContext (__VA_ARGS__); \
      _bsonBuildContextThreadLocalPtr = _bbCtx.parent; \
      _bsonDSL_restoreWarnings ();                     \
   } while (0)

/**
 * @brief Build a BSON document by appending to an existing bson_t document
 *
 * @param Pointer The document upon which to append
 * @param ... The Document elements to append to the document
 */
#define bsonBuildAppend(BSON, ...) _bsonDSL_eval (_bsonBuildAppend (BSON, __VA_ARGS__))
#define _bsonBuildAppend(BSON, ...)                                    \
   _bsonDSL_begin ("Appending to document '%s'", _bsonDSL_str (BSON)); \
   _bsonDSL_disableWarnings ();                                        \
   /* Save the dsl context */                                          \
   struct _bsonBuildContext_t _bbCtx = {                               \
      .doc = &(BSON),                                                  \
      .parent = _bsonBuildContextThreadLocalPtr,                       \
   };                                                                  \
   /* Reset the context */                                             \
   _bsonBuildContextThreadLocalPtr = &_bbCtx;                          \
   bsonBuildError = NULL;                                              \
   _bsonBuildAppendWithCurrentContext (__VA_ARGS__);                   \
   /* Restore the dsl context */                                       \
   _bsonBuildContextThreadLocalPtr = _bbCtx.parent;                    \
   _bsonDSL_restoreWarnings ();                                        \
   _bsonDSL_end

/**
 * @brief Build a new BSON document and assign the value into the given
 * pointer.
 */
#define bsonBuild(BSON, ...)                                              \
   _bsonDSL_begin ("Build a new document for '%s'", _bsonDSL_str (BSON)); \
   bson_t *_bbDest = &(BSON);                                             \
   bson_init (_bbDest);                                                   \
   bsonBuildAppend (*_bbDest, __VA_ARGS__);                               \
   _bsonDSL_end

/**
 * @brief Declare a variable and build it with the BSON DSL @see bsonBuild
 */
#define bsonBuildDecl(Variable, ...)   \
   bson_t Variable = BSON_INITIALIZER; \
   bsonBuild (Variable, __VA_ARGS__)


struct _bsonBuildContext_t {
   /// The document that is being built
   bson_t *doc;
   /// The key that is pending an append
   const char *key;
   /// The length of the string given in 'key'
   int key_len;
   /// The index of the array being built (if applicable)
   size_t index;
   /// A buffer for formatting key strings
   char index_key_str[16];
   /// The parent context (if building a sub-document)
   struct _bsonBuildContext_t *parent;
};

/// A pointer to the current thread's bsonBuild context
_bson_thread_local _bson_comdat struct _bsonBuildContext_t *_bsonBuildContextThreadLocalPtr = NULL;

struct _bsonVisitContext_t {
   const bson_t *doc;
   bson_iter_t iter;
   const struct _bsonVisitContext_t *parent;
   size_t index;
};

/// A pointer to the current thread's bsonVisit/bsonParse context
_bson_thread_local _bson_comdat struct _bsonVisitContext_t const *_bsonVisitContextThreadLocalPtr = NULL;

/**
 * @brief The most recent error from a bsonBuild() DSL command.
 *
 * If NULL, no error occurred. Users can assign a value to this string to
 * indicate failure.
 */
_bson_thread_local _bson_comdat const char *bsonBuildError = NULL;

/**
 * @brief The most recent error from a buildVisit() or bsonParse() DSL command.
 *
 * If NULL, no error occurred. Users can assign a value to this string to
 * indicate an error.
 *
 * If this string becomes non-NULL, the current bsonVisit()/bsonParse() will
 * halt and return.
 *
 * Upon entering a new bsonVisit()/bsonParse(), this will be reset to NULL.
 */
_bson_thread_local _bson_comdat const char *bsonParseError = NULL;

#define _bsonDSLDebug(...) _bson_dsl_debug (BSON_DSL_DEBUG, __FILE__, __LINE__, BSON_FUNC, __VA_ARGS__)


static BSON_INLINE bool
_bson_dsl_test_strequal (const char *string, bool case_sensitive)
{
   bson_iter_t it = bsonVisitIter;
   if (bson_iter_type (&it) == BSON_TYPE_UTF8) {
      uint32_t len;
      const char *s = bson_iter_utf8 (&it, &len);
      if (len != (uint32_t) strlen (string)) {
         return false;
      }
      if (case_sensitive) {
         return memcmp (string, s, len) == 0;
      } else {
         return bson_strcasecmp (string, s) == 0;
      }
   }
   return false;
}

static BSON_INLINE bool
_bson_dsl_key_is_anyof (const char *key, const size_t keylen, int case_sensitive, ...)
{
   va_list va;
   va_start (va, case_sensitive);
   const char *str;
   while ((str = va_arg (va, const char *))) {
      size_t str_len = strlen (str);
      if (str_len != keylen) {
         continue;
      }
      if (case_sensitive) {
         if (memcmp (str, key, str_len) == 0) {
            va_end (va);
            return true;
         }
      } else {
         if (bson_strcasecmp (str, key) == 0) {
            va_end (va);
            return true;
         }
      }
   }
   va_end (va);
   return false;
}

static BSON_INLINE void
_bson_dsl_iter_as_doc (bson_t *into, const bson_iter_t *it)
{
   uint32_t len = 0;
   const uint8_t *dataptr = NULL;
   if (BSON_ITER_HOLDS_ARRAY (it)) {
      bson_iter_array (it, &len, &dataptr);
   } else if (BSON_ITER_HOLDS_DOCUMENT (it)) {
      bson_iter_document (it, &len, &dataptr);
   }
   if (dataptr) {
      BSON_ASSERT (bson_init_static (into, dataptr, len));
   }
}

static BSON_INLINE bool
_bson_dsl_is_empty_bson (const bson_iter_t *it)
{
   bson_t d = BSON_INITIALIZER;
   _bson_dsl_iter_as_doc (&d, it);
   return d.len == 5; // Empty documents/arrays have byte-size of five
}

static BSON_INLINE bool
_bson_dsl_iter_is_last_element (const bson_iter_t *it)
{
   bson_iter_t dup = *it;
   return !bson_iter_next (&dup) && dup.err_off == 0;
}

_bson_thread_local _bson_comdat int _bson_dsl_indent = 0;

static BSON_INLINE void BSON_GNUC_PRINTF (5, 6)
   _bson_dsl_debug (bool do_debug, const char *file, int line, const char *func, const char *string, ...)
{
   if (do_debug) {
      fprintf (stderr, "%s:%d: [%s] bson_dsl: ", file, line, func);
      for (int i = 0; i < _bson_dsl_indent; ++i) {
         fputs ("  ", stderr);
      }
      va_list va;
      va_start (va, string);
      vfprintf (stderr, string, va);
      va_end (va);
      fputc ('\n', stderr);
      fflush (stderr);
   }
}

static BSON_INLINE char *BSON_GNUC_PRINTF (2, 3) _bson_dsl_errorf (char **const into, const char *const fmt, ...)
{
   if (*into) {
      bson_free (*into);
      *into = NULL;
   }
   va_list args;
   va_start (args, fmt);
   *into = bson_strdupv_printf (fmt, args);
   va_end (args);
   return *into;
}

static BSON_INLINE void
_bson_dsl_dupPath (char **into)
{
   if (*into) {
      bson_free (*into);
      *into = NULL;
   }
   char *acc = bson_strdup ("");
   for (const struct _bsonVisitContext_t *ctx = &bsonVisitContext; ctx; ctx = ctx->parent) {
      char *prev = acc;
      if (ctx->parent && BSON_ITER_HOLDS_ARRAY (&ctx->parent->iter)) {
         // We're an array element
         acc = bson_strdup_printf ("[%d]%s", (int) ctx->index, prev);
      } else {
         // We're a document element
         acc = bson_strdup_printf (".%s%s", bson_iter_key (&ctx->iter), prev);
      }
      bson_free (prev);
   }
   *into = bson_strdup_printf ("$%s", acc);
   bson_free (acc);
}

static BSON_INLINE const char *
_bsonVisitIterAs_cstr (void)
{
   return bson_iter_utf8 (&bsonVisitIter, NULL);
}

static BSON_INLINE int32_t
_bsonVisitIterAs_int32 (void)
{
   return bson_iter_int32 (&bsonVisitIter);
}

static BSON_INLINE bool
_bsonVisitIterAs_bool (void)
{
   return bson_iter_as_bool (&bsonVisitIter);
}

static BSON_INLINE bool
_bsonVisitIterAs__Bool (void)
{
   return _bsonVisitIterAs_bool ();
}

#define bsonAs(Type) _bsonDSL_paste (_bsonVisitIterAs_, Type) ()

/// Convert the given argument into a string without inhibitting macro expansion
#define _bsonDSL_str(...) _bsonDSL_str_1 (__VA_ARGS__)
// Empty quotes "" are to ensure a string appears. Old MSVC has a bug
// where empty #__VA_ARGS__ just vanishes.
#define _bsonDSL_str_1(...) "" #__VA_ARGS__

#define _bsonDSL_strElide(MaxLen, ...) \
   (strlen (_bsonDSL_str (__VA_ARGS__)) > (MaxLen) ? "[...]" : _bsonDSL_str (__VA_ARGS__))

/// Paste two tokens:
#define _bsonDSL_paste(a, ...) _bsonDSL_paste_impl (a, __VA_ARGS__)
#define _bsonDSL_paste_impl(a, ...) a##__VA_ARGS__

/// Paste three tokens:
#define _bsonDSL_paste3(a, b, c) _bsonDSL_paste (a, _bsonDSL_paste (b, c))
/// Paste four tokens:
#define _bsonDSL_paste4(a, b, c, d) _bsonDSL_paste (a, _bsonDSL_paste3 (b, c, d))

// clang-format off

/// Now we need a MAP() macro. This idiom is common, but fairly opaque. Below is
/// some crazy preprocessor trickery to implement it. Fortunately, once we have
/// MAP(), the remainder of this file is straightforward. This implementation
/// isn't the simplest one possible, but is one that supports the old
/// non-compliant MSVC preprocessor.

/* Expands to nothing. Used to defer a function-like macro and to ignore arguments */
#define _bsonDSL_nothing(...)

/// Expand to the 64th argument. See below for why this is useful.
#define _bsonDSL_pick64th(\
                    _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, \
                    _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, \
                    _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, \
                    _31, _32, _33, _34, _35, _36, _37, _38, _39, _40, \
                    _41, _42, _43, _44, _45, _46, _47, _48, _49, _50, \
                    _51, _52, _53, _54, _55, _56, _57, _58, _59, _60, \
                    _61, _62, _63, ...) \
    _63

/**
 * @brief Expands to 1 if the given arguments contain any top-level commas, zero otherwise.
 *
 * There is an expansion of __VA_ARGS__, followed by 62 '1' arguments, followed
 * by single '0'. If __VA_ARGS__ contains no commas, pick64th() will return the
 * single zero. If __VA_ARGS__ contains any top-level commas, the series of ones
 * will shift to the right and pick64th will return one of those ones. (This only
 * works __VA_ARGS__ contains fewer than 62 commas, which is a somewhat reasonable
 * limit.) The _bsonDSL_nothing() is a workaround for MSVC's bad preprocessor that
 * expands __VA_ARGS__ incorrectly.
 *
 * If we have __VA_OPT__, this can be a lot simpler.
 */
#define _bsonDSL_hasComma(...) \
    _bsonDSL_pick64th \
    _bsonDSL_nothing() (__VA_ARGS__, \
                        1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, \
                        1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, \
                        1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, \
                        1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, ~)

/**
 * Expands to a single comma if "invoked" as a function-like macro.
 * (This will make sense, I promise.)
 */
#define _bsonDSL_commaIfRHSHasParens(...) ,

/**
 * @brief Expand to 1 if given no arguments, otherwise 0.
 *
 * This could be done much more simply using __VA_OPT__, but we need to work on
 * older compilers.
 */
#define _bsonDSL_isEmpty(...) \
    _bsonDSL_isEmpty_1(\
        /* Expands to '1' if __VA_ARGS__ contains any top-level commas */ \
        _bsonDSL_hasComma(__VA_ARGS__), \
        /* Expands to '1' if __VA_ARGS__ begins with a parenthesis, because \
         * that will cause an "invocation" of _bsonDSL_commaIfRHSHasParens, \
         * which immediately expands to a single comma. */ \
        _bsonDSL_hasComma(_bsonDSL_commaIfRHSHasParens __VA_ARGS__), \
        /* Expands to '1' if __VA_ARGS__ expands to a function-like macro name \
         * that then expands to anything containing a top-level comma */ \
        _bsonDSL_hasComma(__VA_ARGS__ ()), \
        /* Expands to '1' if __VA_ARGS__ expands to nothing. */ \
        _bsonDSL_hasComma(_bsonDSL_commaIfRHSHasParens __VA_ARGS__ ()))

/**
 * A helper for isEmpty(): If given (0, 0, 0, 1), expands as:
 *    - first: _bsonDSL_hasComma(_bsonDSL_isEmpty_CASE_0001)
 *    -  then: _bsonDSL_hasComma(,)
 *    -  then: 1
 * Given any other aruments:
 *    - first: _bsonDSL_hasComma(_bsonDSL_isEmpty_CASE_<somethingelse>)
 *    -  then: 0
 */
#define _bsonDSL_isEmpty_1(_1, _2, _3, _4) \
    _bsonDSL_hasComma(_bsonDSL_paste(_bsonDSL_isEmpty_CASE_, _bsonDSL_paste4(_1, _2, _3, _4)))
#define _bsonDSL_isEmpty_CASE_0001 ,

/**
 * @brief Expand to the first argument if `Cond` is 1, the second argument if `Cond` is 0
 */
#define _bsonDSL_ifElse(Cond, IfTrue, IfFalse) \
    /* Suppress expansion of the two branches by using the '#' operator */ \
    _bsonDSL_nothing(#IfTrue, #IfFalse)  \
    /* Concat the cond 1/0 with a prefix macro: */ \
    _bsonDSL_paste(_bsonDSL_ifElse_PICK_, Cond)(IfTrue, IfFalse)

#define _bsonDSL_ifElse_PICK_1(IfTrue, IfFalse) \
   /* Expand the first operand, throw away the second */ \
   IfTrue _bsonDSL_nothing(#IfFalse)
#define _bsonDSL_ifElse_PICK_0(IfTrue, IfFalse) \
   /* Expand to the second operand, throw away the first */ \
   IfFalse _bsonDSL_nothing(#IfTrue)

#ifdef _MSC_VER
// MSVC's "traditional" preprocessor requires many more expansion passes,
// but GNU and Clang are very slow when evaluating hugely nested expansions
// and generate massive macro expansion backtraces.
#define _bsonDSL_eval_1(...) __VA_ARGS__
#define _bsonDSL_eval_2(...) _bsonDSL_eval_1(_bsonDSL_eval_1(_bsonDSL_eval_1(_bsonDSL_eval_1(_bsonDSL_eval_1(__VA_ARGS__)))))
#define _bsonDSL_eval_4(...) _bsonDSL_eval_2(_bsonDSL_eval_2(_bsonDSL_eval_2(_bsonDSL_eval_2(_bsonDSL_eval_2(__VA_ARGS__)))))
#define _bsonDSL_eval_8(...) _bsonDSL_eval_4(_bsonDSL_eval_4(_bsonDSL_eval_4(_bsonDSL_eval_4(_bsonDSL_eval_4(__VA_ARGS__)))))
#define _bsonDSL_eval_16(...) _bsonDSL_eval_8(_bsonDSL_eval_8(_bsonDSL_eval_8(_bsonDSL_eval_8(_bsonDSL_eval_8(__VA_ARGS__)))))
#define _bsonDSL_eval(...) _bsonDSL_eval_16(_bsonDSL_eval_16(_bsonDSL_eval_16(_bsonDSL_eval_16(_bsonDSL_eval_16(__VA_ARGS__)))))
#else
// Each level of "eval" applies double the expansions of the previous level.
#define _bsonDSL_eval_1(...) __VA_ARGS__
#define _bsonDSL_eval_2(...) _bsonDSL_eval_1(_bsonDSL_eval_1(__VA_ARGS__))
#define _bsonDSL_eval_4(...) _bsonDSL_eval_2(_bsonDSL_eval_2(__VA_ARGS__))
#define _bsonDSL_eval_8(...) _bsonDSL_eval_4(_bsonDSL_eval_4(__VA_ARGS__))
#define _bsonDSL_eval_16(...) _bsonDSL_eval_8(_bsonDSL_eval_8(__VA_ARGS__))
#define _bsonDSL_eval_32(...) _bsonDSL_eval_16(_bsonDSL_eval_16(__VA_ARGS__))
#define _bsonDSL_eval(...) _bsonDSL_eval_32(__VA_ARGS__)
#endif

/**
 * Finally, the Map() macro that allows us to do the magic, which we've been
 * building up to all along.
 *
 * The dance with mapMacro_first, mapMacro_final, and _bsonDSL_nothing
 * conditional on argument count is to prevent warnings from pre-C99 about
 * passing no arguments to the '...' parameters. Yet again, if we had C99 and
 * __VA_OPT__ this would be simpler.
 */
#define _bsonDSL_mapMacro(Action, Constant, ...) \
   /* Pick our first action based on the content of '...': */ \
    _bsonDSL_ifElse( \
      /* If given no arguments: */\
      _bsonDSL_isEmpty(__VA_ARGS__), \
         /* expand to _bsonDSL_nothing */ \
         _bsonDSL_nothing, \
         /* Otherwise, expand to mapMacro_first: */ \
         _bsonDSL_mapMacro_first) \
   /* Now "invoke" the chosen macro: */ \
   _bsonDSL_nothing() (Action, Constant, __VA_ARGS__)

#define _bsonDSL_mapMacro_first(Action, Constant, ...) \
   /* Select our next step based on whether we have one or more arguments: */ \
   _bsonDSL_ifElse( \
      /* If '...' contains more than one argument (has a top-level comma): */ \
      _bsonDSL_hasComma(__VA_ARGS__), \
         /* Begin the mapMacro loop with mapMacro_A: */ \
         _bsonDSL_mapMacro_A, \
         /* Otherwise skip to the final step of the loop: */ \
         _bsonDSL_mapMacro_final) \
   /* Invoke the chosen macro, setting the counter to zero: */ \
   _bsonDSL_nothing() (Action, Constant, 0, __VA_ARGS__)

/// Handle the last expansion in a mapMacro sequence.
#define _bsonDSL_mapMacro_final(Action, Constant, Counter, FinalElement) \
    Action(FinalElement, Constant, Counter)

/**
 * mapMacro_A and mapMacro_B are identical and just invoke each other.
 */
#define _bsonDSL_mapMacro_A(Action, Constant, Counter, Head, ...) \
   /* First evaluate the action once: */ \
   Action(Head, Constant, Counter) \
   /* Pick our next step: */ \
   _bsonDSL_ifElse( \
      /* If '...' contains more than one argument (has a top-level comma): */ \
      _bsonDSL_hasComma(__VA_ARGS__), \
         /* Jump to the other mapMacro: */ \
         _bsonDSL_mapMacro_B, \
         /* Otherwise go to mapMacro_final */ \
         _bsonDSL_mapMacro_final) \
   /* Invoke the next step of the map: */ \
   _bsonDSL_nothing() (Action, Constant, Counter + 1, __VA_ARGS__)

#define _bsonDSL_mapMacro_B(Action, Constant, Counter, Head, ...) \
    Action(Head, Constant, Counter) \
    _bsonDSL_ifElse(_bsonDSL_hasComma(__VA_ARGS__), _bsonDSL_mapMacro_A, _bsonDSL_mapMacro_final) \
    _bsonDSL_nothing() (Action, Constant, Counter + 1, __VA_ARGS__)

// clang-format on


#endif // BSON_BSON_DSL_H_INCLUDED
