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)=>1v(2)=>2ML99_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?