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