Testing, debugging, and error reporting
There is a handy macro for debugging a metaprogram. It is called ML99_abort
:
#define F_IMPL(x, y, z) v(x + y + z)
// abc
ML99_EVAL(ML99_call(F, v(1), v(2), ML99_abort(v(abc))))
ML99_abort
evaluates a provided sequence of expressions and immediately aborts the interpretation. Despite that we call F
, the result of the interpretation is abc
. Here is how Metalang99 evaluates the aforementioned expression:
v(1)
=>1
v(2)
=>2
ML99_abort(v(abc))
=>abc
, then halt.
ML99_abort
is very handy when you need to figure out what is wrong with your metaprogram. I often use the bottom-up approach: first, I ensure that all lower-level macros work as expected, then move on to more general macros, and so on, till I find the problem. To test a specific macro, I call it inside ML99_EVAL(...)
separately from the rest of a metaprogram. With ML99_abort
, I usually trace macro parameters as well as any other interesting terms. You can see the result of interpretation with -E
(GCC/Clang flag; preprocess only).
Secondly, errors need to be somehow emitted. For example, if we try to pop the head of the empty list, we obtain the following compilation error:
playground.c:3:1: error: static assertion failed: "ML99_listHead: expected a non-empty list"
3 | ML99_EVAL(ML99_listHead(ML99_nil()))
| ^~~~~~~~~
To emit such an error, call ML99_fatal
like this:
[playground.c
]
ML99_EVAL(ML99_fatal(F, the description of your error))
[/bin/sh
]
playground.c:3:1: error: static assertion failed: "F: the description of your error"
3 | ML99_EVAL(ML99_fatal(F, the description of your error))
| ^~~~~~~~~
Metalang99 also features the macros ML99_todo
and ML99_unimplemented
as well as their *WithMsg
versions. Using them, you can indicate a not yet implemented and unimplemented functionality, respectively. The difference is that ML99_todo
/ML99_todoWithMsg
convey an intent that some piece of code is to be implemented later, while ML99_unimplemented
/ML99_unimplementedWithMsg
do not make such claims. Consider this code:
#define FOO(...) \
ML99_EVAL(ML99_IF( \
ML99_VARIADICS_IS_SINGLE(__VA_ARGS__), \
v(123), \
ML99_todoWithMsg(v(FOO), v("Multiple arguments are not yet supported"))))
// 123
FOO(1)
// A not-yet-implemented error.
FOO(1, 2, 3)
Here, FOO(1)
works just fine but we are not yet to handle multiple arguments. No problem, just insert ML99_todoWithMsg
and implement it later.
And eventually, if you want to test your macro on certain input values, you can use assertions:
#define F_IMPL(x, y, z) v(x + y + z)
#define CAT_IMPL(x, y) v(x##y)
ML99_ASSERT(v(1 == 1));
ML99_ASSERT_EQ(ML99_call(F, v(1, 2, 3)), v(1 + 2 + 3));
ML99_ASSERT_EMPTY(ML99_call(CAT, v(), v()));
Learn more about assertions here.
Last updated
Was this helpful?