summaryrefslogtreecommitdiff
path: root/libmisc/include
diff options
context:
space:
mode:
Diffstat (limited to 'libmisc/include')
-rw-r--r--libmisc/include/libmisc/_intercept.h17
-rw-r--r--libmisc/include/libmisc/fmt.h156
-rw-r--r--libmisc/include/libmisc/log.h19
-rw-r--r--libmisc/include/libmisc/macro.h107
-rw-r--r--libmisc/include/libmisc/obj.h27
5 files changed, 249 insertions, 77 deletions
diff --git a/libmisc/include/libmisc/_intercept.h b/libmisc/include/libmisc/_intercept.h
index a264144..fa327d6 100644
--- a/libmisc/include/libmisc/_intercept.h
+++ b/libmisc/include/libmisc/_intercept.h
@@ -7,21 +7,18 @@
#ifndef _LIBMISC__INTERCEPT_H_
#define _LIBMISC__INTERCEPT_H_
-/* pico-sdk/newlib define these to be [[gnu:weak]] already, but
+/* pico-sdk/newlib defines abort() to be [[gnu::weak]] already, but
* depending on optimization options glibc might not, and GCC might
- * assume it knows what they do and optimize them out. So define our
- * own `__lm_` wrappers that GCC/glibc won't interfere with.
+ * assume it knows what it does and optimize it out. So define our
+ * own `__lm_` wrapper that GCC/glibc won't interfere with.
*/
-[[gnu::format(printf, 1, 2)]]
-int __lm_printf(const char *format, ...);
-
[[noreturn]] void __lm_abort(void);
-/* __lm_light_printf is expected to have less stack use than regular
- * __lm_printf, if possible. */
+/* While newlib defines putchar() to be [[gnu::weak]], pico_stdio does
+ * not. Plus the above about optimizations.
+ */
-[[gnu::format(printf, 1, 2)]]
-int __lm_light_printf(const char *format, ...);
+void __lm_putchar(unsigned char c);
#endif /* _LIBMISC__INTERCEPT_H_ */
diff --git a/libmisc/include/libmisc/fmt.h b/libmisc/include/libmisc/fmt.h
new file mode 100644
index 0000000..c29c085
--- /dev/null
+++ b/libmisc/include/libmisc/fmt.h
@@ -0,0 +1,156 @@
+/* libmisc/fmt.h - Write formatted text
+ *
+ * Copyright (C) 2025 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+#ifndef _LIBMISC_FMT_H_
+#define _LIBMISC_FMT_H_
+
+#include <stddef.h> /* for size_t */
+#include <stdint.h> /* for (u)int{n}_t */
+
+#include <libmisc/macro.h>
+#include <libmisc/obj.h>
+
+/* destination interface ******************************************************/
+
+#define fmt_dest_LO_IFACE \
+ LO_FUNC(void , putb, uint8_t b) \
+ LO_FUNC(size_t, tell)
+LO_INTERFACE(fmt_dest);
+
+/* type-specific fmt_print_() functions ***************************************/
+
+/* Simple bytes. */
+void fmt_print_byte(lo_interface fmt_dest w, uint8_t b);
+
+/* These are `static inline` so that the compiler can unroll the loops. */
+static inline void fmt_print_mem(lo_interface fmt_dest w, const void *_str, size_t size) {
+ const uint8_t *str = _str;
+ while (size--)
+ fmt_print_byte(w, *(str++));
+}
+static inline void fmt_print_str(lo_interface fmt_dest w, const char *str) {
+ while (*str)
+ fmt_print_byte(w, *(str++));
+}
+static inline void fmt_print_strn(lo_interface fmt_dest w, const char *str, size_t size) {
+ while (size-- && *str)
+ fmt_print_byte(w, *(str++));
+}
+
+/* Quoted bytes. */
+void fmt_print_qbyte(lo_interface fmt_dest w, uint8_t b);
+void fmt_print_qmem(lo_interface fmt_dest w, const void *str, size_t size);
+void fmt_print_qstr(lo_interface fmt_dest w, const char *str);
+void fmt_print_qstrn(lo_interface fmt_dest w, const char *str, size_t size);
+
+/* Integers. */
+#define _fmt_declare_base(base) \
+ void _fmt_print_base##base##_u8(lo_interface fmt_dest w, uint8_t val); \
+ void _fmt_print_base##base##_u16(lo_interface fmt_dest w, uint16_t val); \
+ void _fmt_print_base##base##_u32(lo_interface fmt_dest w, uint32_t val); \
+ void _fmt_print_base##base##_u64(lo_interface fmt_dest w, uint64_t val); \
+ void _fmt_print_base##base##_s8(lo_interface fmt_dest w, int8_t val); \
+ void _fmt_print_base##base##_s16(lo_interface fmt_dest w, int16_t val); \
+ void _fmt_print_base##base##_s32(lo_interface fmt_dest w, int32_t val); \
+ void _fmt_print_base##base##_s64(lo_interface fmt_dest w, int64_t val); \
+ LM_FORCE_SEMICOLON
+_fmt_declare_base(2);
+_fmt_declare_base(8);
+_fmt_declare_base(10);
+_fmt_declare_base(16);
+#undef _fmt_declare_base
+
+#define _fmt_intswitch(val, prefix) _Generic((val) , \
+ unsigned char : LM_CAT3_(prefix, u, __SCHAR_WIDTH__) , \
+ unsigned short : LM_CAT3_(prefix, u, __SHRT_WIDTH__) , \
+ unsigned int : LM_CAT3_(prefix, u, __INT_WIDTH__) , \
+ unsigned long : LM_CAT3_(prefix, u, __LONG_WIDTH__) , \
+ unsigned long long : LM_CAT3_(prefix, u, __LONG_LONG_WIDTH__) , \
+ signed char : LM_CAT3_(prefix, s, __SCHAR_WIDTH__) , \
+ signed short : LM_CAT3_(prefix, s, __SHRT_WIDTH__) , \
+ signed int : LM_CAT3_(prefix, s, __INT_WIDTH__) , \
+ signed long : LM_CAT3_(prefix, s, __LONG_WIDTH__) , \
+ signed long long : LM_CAT3_(prefix, s, __LONG_LONG_WIDTH__) )
+#define fmt_print_base2(w, val) (_fmt_intswitch((val), _fmt_print_base2_)((w), (val)))
+#define fmt_print_base8(w, val) (_fmt_intswitch((val), _fmt_print_base8_)((w), (val)))
+#define fmt_print_base10(w, val) (_fmt_intswitch((val), _fmt_print_base10_)((w), (val)))
+#define fmt_print_base16(w, val) (_fmt_intswitch((val), _fmt_print_base16_)((w), (val)))
+
+/* Booleans. */
+void fmt_print_bool(lo_interface fmt_dest w, bool b);
+
+/* The high-level fmt_print() interface ***************************************/
+
+#define fmt_print(w, ...) do { LM_FOREACH_PARAM_(_fmt_param, (w), __VA_ARGS__) } while (0)
+#define _fmt_param(w, arg) \
+ LM_IF(LM_IS_TUPLE(arg))( \
+ _fmt_param_tuple LM_EAT() (w, LM_EXPAND arg) \
+ )( \
+ _fmt_param_nontuple(w, arg) \
+ );
+#define _fmt_param_tuple(w, fn, ...) fmt_print_##fn(w __VA_OPT__(,) __VA_ARGS__)
+#define _fmt_param_nontuple(w, val) _Generic((val), \
+ unsigned char : LM_CAT2_(_fmt_print_base10_u, __SCHAR_WIDTH__) , \
+ unsigned short : LM_CAT2_(_fmt_print_base10_u, __SHRT_WIDTH__) , \
+ unsigned int : LM_CAT2_(_fmt_print_base10_u, __INT_WIDTH__) , \
+ unsigned long : LM_CAT2_(_fmt_print_base10_u, __LONG_WIDTH__) , \
+ unsigned long long : LM_CAT2_(_fmt_print_base10_u, __LONG_LONG_WIDTH__) , \
+ signed char : LM_CAT2_(_fmt_print_base10_s, __SCHAR_WIDTH__) , \
+ signed short : LM_CAT2_(_fmt_print_base10_s, __SHRT_WIDTH__) , \
+ signed int : LM_CAT2_(_fmt_print_base10_s, __INT_WIDTH__) , \
+ signed long : LM_CAT2_(_fmt_print_base10_s, __LONG_WIDTH__) , \
+ signed long long : LM_CAT2_(_fmt_print_base10_s, __LONG_LONG_WIDTH__) , \
+ char * : fmt_print_str , \
+ const char * : fmt_print_str , \
+ bool : fmt_print_bool )(w, val)
+
+/* print-to-memory ************************************************************/
+
+struct fmt_buf {
+ void *dat;
+ size_t len, cap;
+};
+LO_IMPLEMENTATION_H(fmt_dest, struct fmt_buf, fmt_buf);
+
+#define fmt_snprint(buf, n, ...) ({ \
+ struct fmt_buf _w = { .dat = buf, .cap = n }; \
+ lo_interface fmt_dest w = lo_box_fmt_buf_as_fmt_dest(&_w); \
+ fmt_print(w, __VA_ARGS__); \
+ if (_w.len < _w.cap) \
+ ((char *)_w.dat)[_w.len] = '\0'; \
+ _w.len; \
+})
+
+/* justify ********************************************************************/
+
+/* *grubles about not being allowed to nest things* */
+#define _fmt_param_indirect() _fmt_param
+#define _fmt_print2(w, ...) do { LM_FOREACH_PARAM2_(_fmt_param2, (w), __VA_ARGS__) } while (0)
+#define _fmt_param2(...) _LM_DEFER2(_fmt_param_indirect)()(__VA_ARGS__)
+
+#define fmt_print_ljust(w, width, fillchar, ...) do { \
+ size_t beg = LO_CALL(w, tell); \
+ _fmt_print2(w, __VA_ARGS__); \
+ while ((LO_CALL(w, tell) - beg) < width) \
+ fmt_print_byte(w, fillchar); \
+} while (0)
+
+#define fmt_print_rjust(w, width, fillchar, ...) do { \
+ struct fmt_buf _discard = {}; \
+ lo_interface fmt_dest discard = lo_box_fmt_buf_as_fmt_dest(&_discard); \
+ _fmt_print2(discard, __VA_ARGS__); \
+ while (_discard.len++ < width) \
+ fmt_print_byte(w, fillchar); \
+ _fmt_print2(w, __VA_ARGS__); \
+} while (0)
+
+void fmt_print_base16_u8_(lo_interface fmt_dest w, uint8_t x);
+void fmt_print_base16_u16_(lo_interface fmt_dest w, uint16_t x);
+void fmt_print_base16_u32_(lo_interface fmt_dest w, uint32_t x);
+void fmt_print_base16_u64_(lo_interface fmt_dest w, uint64_t x);
+void fmt_print_ptr(lo_interface fmt_dest w, void *ptr);
+
+#endif /* _LIBMISC_FMT_H_ */
diff --git a/libmisc/include/libmisc/log.h b/libmisc/include/libmisc/log.h
index 4529946..e6dfb52 100644
--- a/libmisc/include/libmisc/log.h
+++ b/libmisc/include/libmisc/log.h
@@ -10,6 +10,7 @@
#include <stdint.h> /* for uint8_t */
#include <libmisc/macro.h>
+#include <libmisc/fmt.h>
#include <libmisc/_intercept.h>
#ifdef NDEBUG
@@ -20,15 +21,17 @@
const char *const_byte_str(uint8_t b);
-#define log_n_errorf(nam, fmt, ...) do { __lm_printf("error: " LM_STR_(nam) ": " fmt "\n" __VA_OPT__(,) __VA_ARGS__); } while (0)
-#define log_n_infof(nam, fmt, ...) do { __lm_printf("info : " LM_STR_(nam) ": " fmt "\n" __VA_OPT__(,) __VA_ARGS__); } while (0)
-#define log_n_debugf(nam, fmt, ...) do { if (LM_CAT3_(CONFIG_, nam, _DEBUG) && !_LOG_NDEBUG) \
- __lm_printf("debug: " LM_STR_(nam) ": " fmt "\n" __VA_OPT__(,) __VA_ARGS__); } while (0)
+extern lo_interface fmt_dest _log_dest;
+
+#define log_n_errorln(nam, ...) fmt_print(_log_dest, "error: " LM_STR_(nam) ": ", __VA_ARGS__, "\n")
+#define log_n_infoln(nam, ...) fmt_print(_log_dest, "info : " LM_STR_(nam) ": ", __VA_ARGS__, "\n")
+#define log_n_debugln(nam, ...) do { if (LM_CAT3_(CONFIG_, nam, _DEBUG) && !_LOG_NDEBUG) \
+ fmt_print(_log_dest, "debug: " LM_STR_(nam) ": ", __VA_ARGS__, "\n"); } while (0)
#endif /* _LIBMISC_LOG_H_ */
-#if defined(LOG_NAME) && !defined(log_errorf)
-#define log_errorf(fmt, ...) log_n_errorf(LOG_NAME, fmt, __VA_ARGS__)
-#define log_infof(fmt, ...) log_n_infof(LOG_NAME, fmt, __VA_ARGS__)
-#define log_debugf(fmt, ...) log_n_debugf(LOG_NAME, fmt, __VA_ARGS__)
+#if defined(LOG_NAME) && !defined(log_errorln)
+#define log_errorln(...) log_n_errorln(LOG_NAME, __VA_ARGS__)
+#define log_infoln(...) log_n_infoln(LOG_NAME, __VA_ARGS__)
+#define log_debugln(...) log_n_debugln(LOG_NAME, __VA_ARGS__)
#endif
diff --git a/libmisc/include/libmisc/macro.h b/libmisc/include/libmisc/macro.h
index 9ac29b9..a2d4264 100644
--- a/libmisc/include/libmisc/macro.h
+++ b/libmisc/include/libmisc/macro.h
@@ -63,6 +63,10 @@
#define LM_FIRST(a, ...) a
#define LM_SECOND(a, b, ...) b
+
+#define LM_FIRST_(...) LM_FIRST(__VA_ARGS__)
+#define LM_SECOND_(...) LM_SECOND(__VA_ARGS__)
+
#define LM_EAT(...)
#define LM_EXPAND(...) __VA_ARGS__
@@ -80,57 +84,88 @@
/* tuples */
-#define LM_IS_TUPLE(x) LM_IS_SENTINEL(_LM_IS_TUPLE x)
-#define _LM_IS_TUPLE(...) LM_SENTINEL()
+#define LM_IS_TUPLE(x) LM_IS_SENTINEL(_LM_IS_TUPLE x)
+#define _LM_IS_TUPLE(...) LM_SENTINEL()
+/* For LM_IS_EMPTY_TUPLE:
+ *
+ * Given
+ *
+ * #define HELPER(...) B, __VA_OPT__(C,) D
+ *
+ * then evaluating the sequence of tokens `HELPER x , A` will give us a
+ * new sequence of tokens according to the following table:
+ *
+ * not a tuple : HELPER x , A
+ * tuple, nonempty: B , C , D , A
+ * tuple, empty : B , D , A
+ *
+ * Looking at this table, it is clear that we must look at the 2nd
+ * resulting comma-separated-value (argument), and set A=false,
+ * C=false, D=true (and B doesn't matter).
+ */
+#define LM_IS_EMPTY_TUPLE(x) LM_SECOND_(_LM_IS_EMPTY_TUPLE x, LM_F)
+#define _LM_IS_EMPTY_TUPLE(...) bogus, __VA_OPT__(LM_F,) LM_T
/* `tuples` is a sequence of `(tuple1)(tuple2)(tuple3)` */
-#define _LM_TUPLES_COMMA(tuple...) (tuple),
-#define LM_TUPLES_NONEMPTY(tuples) LM_IS_TUPLE(_LM_TUPLES_COMMA tuples)
+#define _LM_TUPLES_COMMA(...) (__VA_ARGS__),
+#define LM_TUPLES_IS_NONEMPTY(tuples) LM_IS_TUPLE(_LM_TUPLES_COMMA tuples)
#define LM_TUPLES_HEAD(tuples) LM_EXPAND(LM_FIRST LM_EAT() (_LM_TUPLES_COMMA tuples))
#define LM_TUPLES_TAIL(tuples) LM_EAT tuples
/* iteration */
-/* BUG: LM_FOREACH_TUPLE maxes out at 1024 tuples. */
-#define LM_FOREACH_TUPLE(tuples, func, ...) \
- _LM_EVAL(_LM_FOREACH_TUPLE(tuples, func, __VA_ARGS__))
-#define _LM_FOREACH_TUPLE(tuples, func, ...) \
- LM_IF(LM_TUPLES_NONEMPTY(tuples))( \
- _LM_DEFER2(func)(__VA_ARGS__ __VA_OPT__(,) LM_EXPAND LM_TUPLES_HEAD(tuples)) \
- _LM_DEFER2(_LM_FOREACH_TUPLE_indirect)()(LM_TUPLES_TAIL(tuples), func, __VA_ARGS__) \
- )()
-#define _LM_FOREACH_TUPLE_indirect() _LM_FOREACH_TUPLE
-
-#define _LM_DEFER2(macro) macro LM_EAT LM_EAT()()
-
-#define _LM_EVAL(...) _LM_EVAL__1024(__VA_ARGS__) /* 1024 iterations aught to be enough for anybody */
-#define _LM_EVAL__1024(...) _LM_EVAL__512(_LM_EVAL__512(__VA_ARGS__))
-#define _LM_EVAL__512(...) _LM_EVAL__256(_LM_EVAL__256(__VA_ARGS__))
-#define _LM_EVAL__256(...) _LM_EVAL__128(_LM_EVAL__128(__VA_ARGS__))
-#define _LM_EVAL__128(...) _LM_EVAL__64(_LM_EVAL__64(__VA_ARGS__))
-#define _LM_EVAL__64(...) _LM_EVAL__32(_LM_EVAL__32(__VA_ARGS__))
-#define _LM_EVAL__32(...) _LM_EVAL__16(_LM_EVAL__16(__VA_ARGS__))
+/* The desire to support a high number of iterations is in competition
+ * with the desire for short compile times. 16 is the lowest
+ * power-of-2 for which the current code compiles. */
+#define _LM_EVAL _LM_EVAL__16
#define _LM_EVAL__16(...) _LM_EVAL__8(_LM_EVAL__8(__VA_ARGS__))
#define _LM_EVAL__8(...) _LM_EVAL__4(_LM_EVAL__4(__VA_ARGS__))
#define _LM_EVAL__4(...) _LM_EVAL__2(_LM_EVAL__2(__VA_ARGS__))
#define _LM_EVAL__2(...) _LM_EVAL__1(_LM_EVAL__1(__VA_ARGS__))
#define _LM_EVAL__1(...) __VA_ARGS__
-/** The same as LM_FOREACH_TUPLE(), but callable from inside of LM_FOREACH_TUPLE(). */
-#define LM_FOREACH_TUPLE2(tuples, func, ...) \
- LM_IF(LM_TUPLES_NONEMPTY(tuples))( \
- _LM_DEFER2(func)(__VA_ARGS__ __VA_OPT__(,) LM_EXPAND LM_TUPLES_HEAD(tuples)) \
- _LM_DEFER2(_LM_FOREACH_TUPLE_indirect)()(LM_TUPLES_TAIL(tuples), func, __VA_ARGS__) \
- )()
-#define LM_FOREACH_TUPLE3(tuples, func, ...) \
- LM_IF(LM_TUPLES_NONEMPTY(tuples))( \
- _LM_DEFER2(func)(__VA_ARGS__ __VA_OPT__(,) LM_EXPAND LM_TUPLES_HEAD(tuples)) \
- _LM_DEFER2(_LM_FOREACH_TUPLE_indirect)()(LM_TUPLES_TAIL(tuples), func, __VA_ARGS__) \
- )()
-#define LM_FOREACH_TUPLE4(tuples, func, ...) \
- LM_IF(LM_TUPLES_NONEMPTY(tuples))( \
+#define _LM_DEFER2(macro) macro LM_EAT LM_EAT()()
+
+/**
+ * LM_FOREACH_PARAM(func, (fixedparams), params...) calls
+ * func(fixedparams..., param) for each param.
+ *
+ * BUG: LM_FOREACH_PARAM is limited to (16*2)-1=31 params.
+ */
+#define LM_FOREACH_PARAM(func, fixedparams, ...) _LM_EVAL(_LM_FOREACH_PARAM(func, fixedparams, __VA_ARGS__))
+#define _LM_FOREACH_PARAM(func, fixedparams, ...) _LM_FOREACH_PARAM_ITEM(func, fixedparams, __VA_ARGS__, ())
+#define _LM_FOREACH_PARAM_FIXEDPARAMS(fixedparams) _LM_FOREACH_PARAM_FIXEDPARAMS_inner fixedparams
+#define _LM_FOREACH_PARAM_FIXEDPARAMS_inner(...) __VA_ARGS__ __VA_OPT__(,)
+#define _LM_FOREACH_PARAM_ITEM(func, fixedparams, param, ...) \
+ LM_IF(LM_IS_EMPTY_TUPLE(param))()( \
+ _LM_DEFER2(func)(_LM_FOREACH_PARAM_FIXEDPARAMS(fixedparams) param) \
+ _LM_DEFER2(_LM_FOREACH_PARAM_ITEM_indirect)()(func, fixedparams, __VA_ARGS__) \
+ )
+#define _LM_FOREACH_PARAM_ITEM_indirect() _LM_FOREACH_PARAM_ITEM
+
+/** The same as LM_FOREACH_PARAM(), but callable from inside of LM_FOREACH_PARAM(). */
+#define LM_FOREACH_PARAM2(...) _LM_DEFER2(_LM_FOREACH_PARAM_ITEM_indirect)()(__VA_ARGS__, ())
+
+/** The same as above, but evaluates the arguments first. */
+#define LM_FOREACH_PARAM_(...) LM_FOREACH_PARAM(__VA_ARGS__)
+#define LM_FOREACH_PARAM2_(...) LM_FOREACH_PARAM2(__VA_ARGS__)
+
+/**
+ * LM_FOREACH_TUPLE( (tup1) (tup2) (tup3), func, args...) calls
+ * func(args..., tup...) for each tuple.
+ *
+ * BUG: LM_FOREACH_TUPLE is limited to (16*2)-1=31 tuples.
+ */
+#define LM_FOREACH_TUPLE(tuples, func, ...) \
+ _LM_EVAL(_LM_FOREACH_TUPLE(tuples, func, __VA_ARGS__))
+#define _LM_FOREACH_TUPLE(tuples, func, ...) \
+ LM_IF(LM_TUPLES_IS_NONEMPTY(tuples))( \
_LM_DEFER2(func)(__VA_ARGS__ __VA_OPT__(,) LM_EXPAND LM_TUPLES_HEAD(tuples)) \
_LM_DEFER2(_LM_FOREACH_TUPLE_indirect)()(LM_TUPLES_TAIL(tuples), func, __VA_ARGS__) \
)()
+#define _LM_FOREACH_TUPLE_indirect() _LM_FOREACH_TUPLE
+
+/** The same as LM_FOREACH_TUPLE(), but callable from inside of LM_FOREACH_TUPLE(). */
+#define LM_FOREACH_TUPLE2(...) _LM_DEFER2(_LM_FOREACH_TUPLE_indirect)()(__VA_ARGS__)
#endif /* _LIBMISC_MACRO_H_ */
diff --git a/libmisc/include/libmisc/obj.h b/libmisc/include/libmisc/obj.h
index d30a6f2..6645db7 100644
--- a/libmisc/include/libmisc/obj.h
+++ b/libmisc/include/libmisc/obj.h
@@ -56,17 +56,8 @@
#define _LO_IFACE_VTABLE_lo_nest(_ARG_child_iface_name) const struct _lo_##_ARG_child_iface_name##_vtable *_lo_##_ARG_child_iface_name##_vtable; LM_FOREACH_TUPLE2(_ARG_child_iface_name##_LO_IFACE, _LO_IFACE_VTABLE2)
#define _LO_IFACE_VTABLE_lo_func(_ARG_ret_type, _ARG_func_name, ...) _ARG_ret_type (*_ARG_func_name)(void * __VA_OPT__(,) __VA_ARGS__);
-#define _LO_IFACE_VTABLE2(_tuple_typ, ...) _LO_IFACE_VTABLE2_##_tuple_typ(__VA_ARGS__)
-#define _LO_IFACE_VTABLE2_lo_nest(_ARG_child_iface_name) const struct _lo_##_ARG_child_iface_name##_vtable *_lo_##_ARG_child_iface_name##_vtable; LM_FOREACH_TUPLE3(_ARG_child_iface_name##_LO_IFACE, _LO_IFACE_VTABLE3)
-#define _LO_IFACE_VTABLE2_lo_func(_ARG_ret_type, _ARG_func_name, ...) _ARG_ret_type (*_ARG_func_name)(void * __VA_OPT__(,) __VA_ARGS__);
-
-#define _LO_IFACE_VTABLE3(_tuple_typ, ...) _LO_IFACE_VTABLE3_##_tuple_typ(__VA_ARGS__)
-#define _LO_IFACE_VTABLE3_lo_nest(_ARG_child_iface_name) const struct _lo_##_ARG_child_iface_name##_vtable *_lo_##_ARG_child_iface_name##_vtable; LM_FOREACH_TUPLE4(_ARG_child_iface_name##_LO_IFACE, _LO_IFACE_VTABLE4)
-#define _LO_IFACE_VTABLE3_lo_func(_ARG_ret_type, _ARG_func_name, ...) _ARG_ret_type (*_ARG_func_name)(void * __VA_OPT__(,) __VA_ARGS__);
-
-#define _LO_IFACE_VTABLE4(_tuple_typ, ...) _LO_IFACE_VTABLE4_##_tuple_typ(__VA_ARGS__)
-#define _LO_IFACE_VTABLE4_lo_nest(_ARG_child_iface_name) static_assert(0, "BUG: libmisc/obj.h cannot nest interfaces more than 4 deep");
-#define _LO_IFACE_VTABLE4_lo_func(_ARG_ret_type, _ARG_func_name, ...) _ARG_ret_type (*_ARG_func_name)(void * __VA_OPT__(,) __VA_ARGS__);
+#define _LO_IFACE_VTABLE_indirect() _LO_IFACE_VTABLE
+#define _LO_IFACE_VTABLE2(...) _LM_DEFER2(_LO_IFACE_VTABLE_indirect)()(__VA_ARGS__)
#define _LO_IFACE_PROTO(_ARG_iface_name, _tuple_typ, ...) _LO_IFACE_PROTO_##_tuple_typ(_ARG_iface_name, __VA_ARGS__)
#define _LO_IFACE_PROTO_lo_nest(_ARG_iface_name, _ARG_child_iface_name) \
@@ -157,17 +148,7 @@
#define _LO_IMPL_VTABLE_lo_nest(_ARG_impl_name, _ARG_child_iface_name) ._lo_##_ARG_child_iface_name##_vtable = &_lo_##_ARG_impl_name##_##_ARG_child_iface_name##_vtable, LM_FOREACH_TUPLE2(_ARG_child_iface_name##_LO_IFACE, _LO_IMPL_VTABLE2, _ARG_impl_name)
#define _LO_IMPL_VTABLE_lo_func(_ARG_impl_name, _ARG_ret_type, _ARG_func_name, ...) ._ARG_func_name = (void*)_ARG_impl_name##_##_ARG_func_name,
-#define _LO_IMPL_VTABLE2(_ARG_impl_name, _tuple_typ, ...) _LO_IMPL_VTABLE2_##_tuple_typ(_ARG_impl_name, __VA_ARGS__)
-#define _LO_IMPL_VTABLE2_lo_nest(_ARG_impl_name, _ARG_child_iface_name) ._lo_##_ARG_child_iface_name##_vtable = &_lo_##_ARG_impl_name##_##_ARG_child_iface_name##_vtable, LM_FOREACH_TUPLE3(_ARG_child_iface_name##_LO_IFACE, _LO_IMPL_VTABLE3, _ARG_impl_name)
-#define _LO_IMPL_VTABLE2_lo_func(_ARG_impl_name, _ARG_ret_type, _ARG_func_name, ...) ._ARG_func_name = (void*)_ARG_impl_name##_##_ARG_func_name,
-
-#define _LO_IMPL_VTABLE3(_ARG_impl_name, _tuple_typ, ...) _LO_IMPL_VTABLE3_##_tuple_typ(_ARG_impl_name, __VA_ARGS__)
-#define _LO_IMPL_VTABLE3_lo_nest(_ARG_impl_name, _ARG_child_iface_name) ._lo_##_ARG_child_iface_name##_vtable = &_lo_##_ARG_impl_name##_##_ARG_child_iface_name##_vtable, LM_FOREACH_TUPLE4(_ARG_child_iface_name##_LO_IFACE, _LO_IMPL_VTABLE4, _ARG_impl_name)
-#define _LO_IMPL_VTABLE3_lo_func(_ARG_impl_name, _ARG_ret_type, _ARG_func_name, ...) ._ARG_func_name = (void*)_ARG_impl_name##_##_ARG_func_name,
-
-#define _LO_IMPL_VTABLE4(_ARG_impl_name, _tuple_typ, ...) _LO_IMPL_VTABLE4_##_tuple_typ(_ARG_impl_name, __VA_ARGS__)
-#define _LO_IMPL_VTABLE4_lo_nest(_ARG_impl_name, _ARG_child_iface_name) static_assert(0, "BUG: libmisc/obj.h cannot nest interfaces more than 4 deep");
-#define _LO_IMPL_VTABLE4_lo_func(_ARG_impl_name, _ARG_ret_type, _ARG_func_name, ...) ._ARG_func_name = (void*)_ARG_impl_name##_##_ARG_func_name,
-
+#define _LO_IMPL_VTABLE_indirect() _LO_IMPL_VTABLE
+#define _LO_IMPL_VTABLE2(...) _LM_DEFER2(_LO_IMPL_VTABLE_indirect)()(__VA_ARGS__)
#endif /* _LIBMISC_OBJ_H_ */