chiark / gitweb /
@@@ more mess
[mLib] / utils / macros.h
index b96047589a08b94cb937cccaeef4704d277d2685..2ca5a06c2d316deb25d98f4baa865f2dbe98f10e 100644 (file)
   extern "C" {
 #endif
 
   extern "C" {
 #endif
 
+/*----- Header files ------------------------------------------------------*/
+
+#include <assert.h>
+
+#ifndef MLIB_COMPILER_H
+#  include "compiler.h"
+#endif
+
 /*----- Miscellaneous utility macros --------------------------------------*/
 
 /*----- Miscellaneous utility macros --------------------------------------*/
 
-#define N(v) (sizeof(v)/sizeof(*v))
+/* --- @N@ --- *
+ *
+ * Arguments:  @type v[]@ = an actual array, not a pointer
+ *
+ * Returns:    The number of elements in @v@.
+ */
+
+#define N(v) (sizeof(v)/sizeof(*(v)))
+
+/* --- @STR@ --- *
+ *
+ * Arguments:  @x@ = some tokens
+ *
+ * Returns:    A string literal containing the macro-expanded text of @x@.
+ */
 
 #define MLIB__STR(x) #x
 #define STR(x) MLIB__STR(x)
 
 
 #define MLIB__STR(x) #x
 #define STR(x) MLIB__STR(x)
 
+/* --- @GLUE@, @GLUE3@ --- *
+ *
+ * Arguments:  @x, y@ = two sequences of tokens
+ *             @z@ = a third sequence of tokens
+ *
+ * Returns:    A single token formed by gluing together the macro-expansions
+ *             of @x@ and @y@, and @z@ for @GLUE3@.
+ */
+
 #define MLIB__GLUE(x, y) x##y
 #define GLUE(x, y) MLIB__GLUE(x, y)
 #define MLIB__GLUE(x, y) x##y
 #define GLUE(x, y) MLIB__GLUE(x, y)
+#define GLUE3(x, y, z) GLUE(x, MLIB__GLUE(y, z))
+
+/* --- @STATIC_ASSERT@ --- *
+ *
+ * Arguments:  @int cond@ = a condition
+ *             @msg@ = a string literal message
+ *
+ * Returns:    ---
+ *
+ * Use:                Fail at compile time unless @cond@ is nonzero.  The failure
+ *             might report @msg@.
+ */
+
+#ifdef static_assert
+#  define STATIC_ASSERT(cond, msg) static_assert(!!(cond), msg)
+#else
+#  define STATIC_ASSERT(cond, msg)                                     \
+       IGNORABLE extern char static_assert_failed[1 - 2*!(cond)]
+#endif
 
 
-/*----- Compiler diagnostics ----------------------------------------------*/
-
-/* --- Compiler-specific definitions --- */
-
-#if defined(__GNUC__)
-
-#  define GCC_VERSION_P(maj, min)                                      \
-       (__GNUC__ > (maj) || (__GNUC__ == (maj) && __GNUC_MINOR__ >= (min)))
-
-#  if GCC_VERSION_P(2, 5)
-#    define NORETURN __attribute__((noreturn))
-#    define PRINTF_LIKE(fix, aix) __attribute__((format(printf, fix, aix)))
-#    define SCANF_LIKE(fix, aix) __attribute__((format(scanf, fix, aix)))
-#    define IGNORABLE __attribute__((unused))
-#  endif
-
-#  if GCC_VERSION_P(4, 5)
-#    define DEPRECATED(msg) __attribute__((deprecated(msg)))
-#  elif GCC_VERSION_P(3, 1)
-#    define DEPRECATED(msg) __attribute__((deprecated))
-#  endif
-
-#  if GCC_VERSION_P(4, 0)
-#    define EXECL_LIKE(ntrail) __attribute__((sentinel(ntrail)))
-#  endif
-
-#  if GCC_VERSION_P(4, 6)
-
-     /* --- Diagnostic suppression in GCC: a tale of woe --- *
-      *
-      * This is extremely unpleasant, largely as a result of bugs in the GCC
-      * preprocessor's handling of @_Pragma@.  The fundamental problem is
-      * that it's the preprocessor, and not the compiler proper, which
-      * detects @_Pragma@, emitting @#pragma@ lines into its output; and it
-      * does it during macro expansion, even if the macro is being expanded
-      * during argument collection.  Since arguments are expanded before
-      * replacing the macro's invocation with its body, a pragma in an
-      * argument will be emitted %%\emph{before}%% any pragmata in the body,
-      * even if they appear before the argument in the body -- and even if
-      * the argument doesn't actually appear anywhere at all in the body.
-      *
-      * Another, rather less significant, problem is that @_Pragma@'s
-      * argument is a single string literal, recognized in translation phase
-      * 4, before string-literal concatenation in phase 6, so we must build
-      * pragma bodies as token lists and then stringify them.
-      *
-      * As a result, we need some subterfuge here.  The @MLIB__PRAGMA_HACK@
-      * macro issues a @_Pragma@ on its argument token list, which it
-      * stringifies; this deals with the second problem.  The first is
-      * trickier: we must delay expansion of @MLIB__PRAGMA_HACK@ from the
-      * argument collection phase to the body rescanning phase, and we do
-      * this by splitting the invocations between @GCC_WARNING@ macro calls:
-      * the name is left hanging from the previous call (or from
-      * @MLIB__MUFFLE_WARNINGS@, in the first case) and the body is supplied
-      * by @GCC_WARNING@, which also supplies the next @MLIB__PRAGMA_HACK@.
-      * The remaining problem is to make sure we can dispose of the final
-      * trailing @MLIB__PRAGMA_HACK@ harmlessly, which we do by introducing
-      * an extra argument @emitp@, which may be either @t@ or @nil@; this
-      * dispatches to an appropriate helper macro by means of token-pasting.
-      *
-      * I'm so sorry.
-      */
-
-#    define MLIB__PRAGMA_HACK_t(x) _Pragma(#x)
-#    define MLIB__PRAGMA_HACK_nil(x)
-#    define MLIB__PRAGMA_HACK(emitp, x) MLIB__PRAGMA_HACK_##emitp(x)
-#    define MLIB__MUFFLE_WARNINGS(warns, body)                         \
+/* --- @CHECK_TYPE@ ---
+ *
+ * Arguments:  @expty@ = expected type of @x@
+ *             @expty x@ = some object
+ *
+ * Returns:    Integer zero.
+ *
+ * Use:                Cause a compile-time failure unless the type of @x@ is
+ *             assignment-compatible with @expty@.
+ */
+
+#define CHECK_TYPE(expty, x) (!sizeof(*(expty *)0 = (x)))
+
+/* --- @CONVERT_CAREFULLY@ ---
+ *
+ * Arguments:  @newty@ = new type for the result
+ *             @expty@ = expected type of @x@
+ *             @expty x@ = some object
+ *
+ * Returns:    @x@, but coerced to type @newty@.
+ *
+ * Use:                Like @(newty)x@, except that it checks at compile-time that
+ *             @x@ is at least assignment-compatible with type @expty@
+ *             before trying.
+ */
+
+#define CONVERT_CAREFULLY(newty, expty, x)                             \
+       (CHECK_TYPE(expty, x) + (/*unconst unvolatile*/ newty)(x))
+
+/* --- @UNCONST@, @UNVOLATILE@, @UNQUALIFY@ --- *
+ *
+ * Arguments:  @type@ = a type name
+ *             @type *p@ = a pointer
+ *
+ * Returns:    @p@, but without @const@, @volatile@ or both qualifiers.
+ *
+ * Use:                Strips qualifiers from pointer types.
+ *
+ *             The @UNCONST@ macro strips @const@.  It checks that @p@
+ *             has type `pointer to @type@ or @const type@'; if not, a
+ *             compile-time error results.  Otherwise, it returns the value
+ *             of @p@, converted to `pointer to (non-constant) @type@'.  It
+ *             will not silently strip a @volatile@ qualifier.
+ *
+ *             The @UNVOLATILE@ macro is similar, except that it strips
+ *             @volatile@ instead of @const@.  The @UNQUALIFY@ macro strips
+ *             both qualifiers.
+ */
+
+#define UNCONST(type, p) CONVERT_CAREFULLY(type *, const type *, p)
+#define UNVOLATILE(type, p) CONVERT_CAREFULLY(type *, volatile type *, p)
+#define UNQUALIFY(type, p)                                             \
+       CONVERT_CAREFULLY(type *, const volatile type *, p)
+
+/* --- @EMPTY@ --- *
+ *
+ * Arguments:  ---
+ *
+ * Returns:    The empty token sequence.
+ */
+
+#define EMPTY
+
+/* --- @COMMA@ --- *
+ *
+ * Arguments:  ---
+ *
+ * Returns:    A `%|,|%' token, which can be usefully passed to macros to
+ *             avoid argument splitting.
+ */
+
+#define COMMA ,
+
+/*----- String and character hacks ----------------------------------------*/
+
+/* --- @IS...@ --- *
+ *
+ * Arguments:  @int ch@ = a character code, but not @EOF@
+ *
+ * Returns:    Nonzero if @ch@ is in the relevant @<ctype.h>@ category.
+ *
+ * Use:                Classifies characters, but safely even if characters are
+ *             signed.
+ *
+ *             There is a macro for each of the @<ctype.h>@ @is...@
+ *             functions.
+ */
+
+#define CTYPE_HACK(func, ch) (func((unsigned char)(ch)))
+
+#define ISALNUM(ch) CTYPE_HACK(isalnum, ch)
+#define ISALPHA(ch) CTYPE_HACK(isalpha, ch)
+#define ISASCII(ch) CTYPE_HACK(isascii, ch)
+#define ISBLANK(ch) CTYPE_HACK(isblank, ch)
+#define ISCNTRL(ch) CTYPE_HACK(iscntrl, ch)
+#define ISDIGIT(ch) CTYPE_HACK(isdigit, ch)
+#define ISGRAPH(ch) CTYPE_HACK(isgraph, ch)
+#define ISLOWER(ch) CTYPE_HACK(islower, ch)
+#define ISPRINT(ch) CTYPE_HACK(isprint, ch)
+#define ISPUNCT(ch) CTYPE_HACK(ispunct, ch)
+#define ISSPACE(ch) CTYPE_HACK(isspace, ch)
+#define ISUPPER(ch) CTYPE_HACK(isupper, ch)
+#define ISXDIGIT(ch) CTYPE_HACK(isxdigit, ch)
+
+/* --- @TO...@ --- *
+ *
+ * Arguments:  @int ch@ = a character code, but not @EOF@
+ *
+ * Returns:    The converted character code.
+ *
+ * Use:                Converts characters, but safely even if characters are
+ *             signed.
+ *
+ *             There is a macro for each of the @<ctype.h>@ @to...@
+ *             functions.
+ */
+
+#define TOASCII(ch) CTYPE_HACK(toascii, ch)
+#define TOLOWER(ch) CTYPE_HACK(tolower, ch)
+#define TOUPPER(ch) CTYPE_HACK(toupper, ch)
+
+/* --- @MEMCMP@, @STRCMP@, @STRNCMP@ --- *
+ *
+ * Arguments:  @const type *x, *y@ = pointers to strings
+ *             @op@ = a relational operator symbol
+ *             @size_t n@ = length of the strings
+ *
+ * Returns:    Nonzero if the relationship between the strings satisfies the
+ *             operator @op@, otherwise zero.
+ *
+ * Use:                These macros mitigate the author's frequent error of failing
+ *             to compare the result of the underlying standard functions
+ *             against zero, effectively reversing the sense of an intended
+ *             test for equality.
+ */
+
+#define MEMCMP(x, op, y, n) (memcmp((x), (y), (n)) op 0)
+#define STRCMP(x, op, y) (strcmp((x), (y)) op 0)
+#define STRNCMP(x, op, y, n) (strncmp((x), (y), (n)) op 0)
+
+/*----- Compiler-specific definitions -------------------------------------*/
+
+/* The descriptions of these are given below, with the fallback
+ * definitions.
+ */
+
+#if GCC_VERSION_P(2, 5) || CLANG_VERSION_P(3, 3)
+#  define NORETURN __attribute__((__noreturn__))
+#  define PRINTF_LIKE(fix, aix) __attribute__((__format__(printf, fix, aix)))
+#  define SCANF_LIKE(fix, aix) __attribute__((__format__(scanf, fix, aix)))
+#  define IGNORABLE __attribute__((__unused__))
+#endif
+
+#if GCC_VERSION_P(3, 4) || CLANG_VERSION_P(3, 3)
+#  define MUST_CHECK __attribute__((__warn_unused_result__))
+#endif
+
+#if GCC_VERSION_P(4, 5) || CLANG_VERSION_P(3, 3)
+#  define DEPRECATED(msg) __attribute__((__deprecated__(msg)))
+#elif GCC_VERSION_P(3, 1)
+#  define DEPRECATED(msg) __attribute__((__deprecated__))
+#endif
+
+#if GCC_VERSION_P(4, 0) || CLANG_VERSION_P(3, 3)
+#  define EXECL_LIKE(ntrail) __attribute__((__sentinel__(ntrail)))
+#endif
+
+#if GCC_VERSION_P(2, 7) || CLANG_VERSION_P(0, 0)
+#  define LAUNDER(x)                                                   \
+       ({ __typeof__(x) _y; __asm__("" : "=g"(_y) : "0"(x)); _y; })
+#  define ADMIRE(x)                                                    \
+       ({ __asm__("" :: "g"(x)); })
+#  define ADMIRE_BUF(p, sz)                                            \
+       ({ __asm__("" :: "m"(*(unsigned char *)p), "g"(sz) : "memory"); })
+#  define RELAX do __asm__(""); while (0)
+#endif
+
+#if CLANG_VERSION_P(3, 3)
+
+#  define MLIB__PRAGMA_HACK(x) _Pragma(#x)
+#  define MLIB__MUFFLE_WARNINGS(warns, body)                           \
+       _Pragma("clang diagnostic push")                                \
+       warns                                                           \
+       body                                                            \
+       _Pragma("clang diagnostic pop")
+#  define CLANG_WARNING(warn)                                          \
+       MLIB__PRAGMA_HACK(clang diagnostic ignored warn)
+#  define MUFFLE_WARNINGS_DECL(warns, body)                            \
+       MLIB__MUFFLE_WARNINGS(warns, body)
+#  define MUFFLE_WARNINGS_EXPR(warns, body)                            \
+       __extension__ ({ MLIB__MUFFLE_WARNINGS(warns, (body);) })
+#  define MUFFLE_WARNINGS_STMT(warns, body)                            \
+       do { MLIB__MUFFLE_WARNINGS(warns, body) } while (0)
+
+#endif
+
+#if GCC_VERSION_P(4, 6)
+
+   /* --- Diagnostic suppression in GCC: a tale of woe --- *
+    *
+    * This is extremely unpleasant, largely as a result of bugs in the GCC
+    * preprocessor's handling of @_Pragma@.  The fundamental problem is
+    * that it's the preprocessor, and not the compiler proper, which
+    * detects @_Pragma@, emitting @#pragma@ lines into its output; and it
+    * does it during macro expansion, even if the macro is being expanded
+    * during argument collection.  Since arguments are expanded before
+    * replacing the macro's invocation with its body, a pragma in an
+    * argument will be emitted %%\emph{before}%% any pragmata in the body,
+    * even if they appear before the argument in the body -- and even if
+    * the argument doesn't actually appear anywhere at all in the body.
+    *
+    * Another, rather less significant, problem is that @_Pragma@'s
+    * argument is a single string literal, recognized in translation phase
+    * 4, before string-literal concatenation in phase 6, so we must build
+    * pragma bodies as token lists and then stringify them.
+    *
+    * As a result, we need some subterfuge here.  The @MLIB__PRAGMA_HACK@
+    * macro issues a @_Pragma@ on its argument token list, which it
+    * stringifies; this deals with the second problem.  The first is
+    * trickier: we must delay expansion of @MLIB__PRAGMA_HACK@ from the
+    * argument collection phase to the body rescanning phase, and we do
+    * this by splitting the invocations between @GCC_WARNING@ macro calls:
+    * the name is left hanging from the previous call (or from
+    * @MLIB__MUFFLE_WARNINGS@, in the first case) and the body is supplied
+    * by @GCC_WARNING@, which also supplies the next @MLIB__PRAGMA_HACK@.
+    * The remaining problem is to make sure we can dispose of the final
+    * trailing @MLIB__PRAGMA_HACK@ harmlessly, which we do by introducing
+    * an extra argument @emitp@, which may be either @t@ or @nil@; this
+    * dispatches to an appropriate helper macro by means of token-pasting.
+    *
+    * I'm so sorry.
+    */
+
+#  define MLIB__PRAGMA_HACK_t(x) _Pragma(#x)
+#  define MLIB__PRAGMA_HACK_nil(x)
+#  define MLIB__PRAGMA_HACK(emitp, x) MLIB__PRAGMA_HACK_##emitp(x)
+#  define MLIB__MUFFLE_WARNINGS(warns, body)                           \
        _Pragma("GCC diagnostic push") MLIB__PRAGMA_HACK                \
        warns                                                           \
        (nil, nil)                                                      \
        body                                                            \
        _Pragma("GCC diagnostic pop")
        _Pragma("GCC diagnostic push") MLIB__PRAGMA_HACK                \
        warns                                                           \
        (nil, nil)                                                      \
        body                                                            \
        _Pragma("GCC diagnostic pop")
-#    define GCC_WARNING(warn)                                          \
+#  define GCC_WARNING(warn)                                            \
        (t, GCC diagnostic ignored warn) MLIB__PRAGMA_HACK
        (t, GCC diagnostic ignored warn) MLIB__PRAGMA_HACK
-#    define MUFFLE_WARNINGS_DECL(warns, body)                          \
+#  define MUFFLE_WARNINGS_DECL(warns, body)                            \
        MLIB__MUFFLE_WARNINGS(warns, body)
        MLIB__MUFFLE_WARNINGS(warns, body)
-#    define MUFFLE_WARNINGS_EXPR(warns, body)                          \
+#  define MUFFLE_WARNINGS_EXPR(warns, body)                            \
        __extension__ ({ MLIB__MUFFLE_WARNINGS(warns, (body);) })
        __extension__ ({ MLIB__MUFFLE_WARNINGS(warns, (body);) })
-#    define MUFFLE_WARNINGS_STMT(warns, body)                          \
+#  define MUFFLE_WARNINGS_STMT(warns, body)                            \
        do { MLIB__MUFFLE_WARNINGS(warns, body) } while (0)
        do { MLIB__MUFFLE_WARNINGS(warns, body) } while (0)
-#  endif
-
 #endif
 
 /* --- Fallback definitions, mostly trivial --- */
 
 #endif
 
 /* --- Fallback definitions, mostly trivial --- */
 
-#ifndef GCC_VERSION_P
-#  define GCC_VERSION_P(maj, min) 0
+/* --- @DISCARD@ --- *
+ *
+ * Arguments:  @x@ = a function call
+ *
+ * Returns:    ---
+ *
+ * Use:                Explicitly discard the result of @x@.  This counteracts a
+ *             @MUST_CHECK@ attribute on the called function.
+ */
+
+#ifndef DISCARD
+#  define DISCARD(x) do if (x); while (0)
 #endif
 
 #endif
 
-#ifndef DEPRECATED
-#  define DEPRECATED(msg)
+/* --- @IGNORE@ --- *
+ *
+ * Arguments:  @x@ = any expression
+ *
+ * Returns:    ---
+ *
+ * Use:                Ignore the value of @x@, overriding compiler warnings.
+ */
+
+#ifndef IGNORE
+#  define IGNORE(x) ((void)(x))
 #endif
 
 #endif
 
-#ifndef EXECL_LIKE
-#  define EXECL_LIKE(ntrail)
+/* --- @LAUNDER@ --- *
+ *
+ * Arguments:  @x@ = some integer expression
+ *
+ * Returns:    @x@.
+ *
+ * Use:                Causes a compiler to know nothing about the value of @x@,
+ *             even if it looks obvious, e.g., it's a constant.
+ */
+
+#ifndef LAUNDER
+#  define LAUNDER(x) (x)
 #endif
 
 #endif
 
-#ifndef DISCARD
-#  define DISCARD(x) do if (x); while (0)
+/* --- @ADMIRE@, @ADMIRE_BUF@ --- *
+ *
+ * Arguments:  @x@ = some scalar expression
+ *             @const void *p@, @size_t sz@ = a pointer and length
+ *
+ * Returns:    ---
+ *
+ * Use:                Ensures that the compiler generates code to compute @x@ or
+ *             the contents of the buffer at @p@.
+ */
+
+#ifndef ADMIRE
+#  define ADMIRE(x) ((void)(x))
+#endif
+#ifndef ADMIRE_BUF
+#  define ADMIRE_BUF(p, sz) ((void)(p), (void)(sz))
 #endif
 
 #endif
 
-#ifndef IGNORE
-#  define IGNORE(x) ((void)(x))
+/* --- @RELAX@ --- *
+ *
+ * Arguments:  ---
+ *
+ * Returns:    ---
+ *
+ * Use:                Does nothing, but the compiler doesn't know that.
+ */
+
+#ifndef RELAX
+#  define RELAX
 #endif
 
 #endif
 
-#ifndef MUFFLE_WARNINGS_DECL
-#  define MUFFLE_WARNINGS_DECL(warns, body) body
+/* --- @DEPRECATED@, @NORETURN@, @IGNORABLE@, @MUST_CHECK@ --- *
+ *
+ * Use:                These are (mostly) function attributes; write them among the
+ *             declaration specifiers for a function definition or
+ *             declaration.  These may not do anything, but the intended
+ *             behaviour is as follows.
+ *
+ *               * @DEPRECATED(msg)@ -- report a warning, quoting the string
+ *                 literal @msg@, if the function is called.
+ *
+ *               * @NORETURN@ -- promise that the function doesn't return to
+ *                 its caller: either it kills the process, or it performs
+ *                 some nonlocal transfer.
+ *
+ *               * @IGNORABLE@ -- the item (which might be data rather than
+ *                 a function) might not be referred to, but that's OK:
+ *                 don't warn about it.
+ *
+ *               @ @MUST_CHECK@ -- warn if the return value of a function is
+ *                 ignored.  Use @DISCARD@ if you really don't care.
+ */
+
+#ifndef DEPRECATED
+#  define DEPRECATED(msg)
 #endif
 
 #endif
 
-#ifndef MUFFLE_WARNINGS_EXPR
-#  define MUFFLE_WARNINGS_EXPR(warns, body) (body)
+#ifndef NORETURN
+#  define NORETURN
 #endif
 
 #endif
 
-#ifndef MUFFLE_WARNINGS_STMT
-#  define MUFFLE_WARNINGS_STMT(warns, body) do { body } while (0)
+#ifndef IGNORABLE
+#  define IGNORABLE
 #endif
 
 #endif
 
+#ifndef MUST_CHECK
+#  define MUST_CHECK
+#endif
+
+/* --- @PRINTF_LIKE@, @SCANF_LIKE@, @EXECL_LIKE@ --- *
+ *
+ * Arguments:  @int fmtix@ = format string argument index (starting from 1)
+ *             @int argix@ = variable format argument tail index (starting
+ *                     from 1)
+ *             @int ntrail@ = number of arguments following terminator
+ *
+ * Use:                These are function attributes.  Again, they might not do
+ *             anything at all.  By intention, they give the compiler
+ *             information about a variadic function's arguments, so that it
+ *             can warn about misuse.
+ *
+ *               * @PRINTF_LIKE@ -- the function takes a @printf@-style
+ *                 format string as argument @fmtix@ and an argument tail
+ *                 (which may be empty) beginning with argument @argix@.
+ *
+ *               * @SCANF_LIKE@ -- the function takes a @scanf@-style
+ *                 format string as argument @fmtix@ and an argument tail
+ *                 (which may be empty) beginning with argument @argix@.
+ *
+ *               * @EXECL_LIKE@ -- the function takes a sequence of pointer
+ *                 arguments terminated by a null pointer, followed by
+ *                 @ntrail@ further arguments.
+ */
+
 #ifndef PRINTF_LIKE
 #  define PRINF_LIKE(fmtix, argix)
 #endif
 #ifndef PRINTF_LIKE
 #  define PRINF_LIKE(fmtix, argix)
 #endif
 #  define SCANF_LIKE(fmtix, argix)
 #endif
 
 #  define SCANF_LIKE(fmtix, argix)
 #endif
 
-#ifndef IGNORABLE
-#  define IGNORABLE
+#ifndef EXECL_LIKE
+#  define EXECL_LIKE(ntrail)
+#endif
+
+/* --- @MUFFLE_WARNINGS_...@ --- *
+ *
+ * Arguments:  @warns@ = a sequence of @..._WARNING@ calls (see below)
+ *             @body@ = some program text
+ *
+ * Use:                Muffle specific warnings within the program text.
+ *
+ *             For @MUFFLE_WARNINGS_DECL@, the program text is a
+ *             declaration; for @MUFFLE_WARNINGS_EXPR@, it is an expression,
+ *             and for @MUFFLE_WARNINGS_STMT@, it is a statement.
+ *
+ *             The warnings to be muffled are given as a list of
+ *             @..._WARNING@ macros, with no separators.  The list can
+ *             list warnings from multiple different compilers: entries for
+ *             irrelevant compilers will be ignored.
+ */
+
+#ifndef MUFFLE_WARNINGS_DECL
+#  define MUFFLE_WARNINGS_DECL(warns, body) body
+#endif
+
+#ifndef MUFFLE_WARNINGS_EXPR
+#  define MUFFLE_WARNINGS_EXPR(warns, body) (body)
+#endif
+
+#ifndef MUFFLE_WARNINGS_STMT
+#  define MUFFLE_WARNINGS_STMT(warns, body) do { body } while (0)
 #endif
 
 #endif
 
+/* --- @GCC_WARNING@ --- *
+ *
+ * Arguments:  @warn@ = a string literal naming a warning, with `%|-W...|%'
+ *                     prefix
+ *
+ * Use:                Names a GCC warning: use within @MUFFLE_WARNINGS_...@.
+ *
+ *             Note that GCC's warning suppression is very buggy.
+ */
+
 #ifndef GCC_WARNING
 #  define GCC_WARNING(warn)
 #endif
 
 #ifndef GCC_WARNING
 #  define GCC_WARNING(warn)
 #endif
 
+/* --- @CLANG_WARNING@ --- *
+ *
+ * Arguments:  @warn@ = a string literal naming a warning, with `%|-W...|%'
+ *                     prefix
+ *
+ * Use:                Names a Clang warning: use within @MUFFLE_WARNINGS_...@.
+ */
+
+#ifndef CLANG_WARNING
+#  define CLANG_WARNING(warn)
+#endif
+
 /*----- That's all, folks -------------------------------------------------*/
 
 #ifdef __cplusplus
 /*----- That's all, folks -------------------------------------------------*/
 
 #ifdef __cplusplus