Macros


strawberry

Strawberry • A delicious macro.

These head and tail functions do the correct thing, but the code is pretty unclear, and long. There is so much error checking that the functionality is hard to see. One method we can use to clean it up is to use a Macro.

A Macro is a preprocessor statement for creating function-like-things that are evaluated before the program is compiled. It can be used for many different things, one of which is what we need to do here, clean up code.

Macros work by taking some arguments (which can be almost anything), and copying and pasting them into some given pattern. By changing the pattern or the arguments we can alter what code is generated by the Macro. To define macros we use the #define preprocessor directive. After this we write the name of the macro, followed by the argument names in parenthesis. After this the pattern is specified, to declare what code should be generated for the given arguments.

We can design a macro to help with our error conditions called LASSERT. Macros are typically given names in capitals to help distinguish them from normal C functions. This macro take in three arguments args, cond and err. It then generates code as shown on the right hand side, but with these variables pasted in at the locations where they are named. This pattern is a good fit for all of our error conditions.

  1. #define LASSERT(args, cond, err) \
  2. if (!(cond)) { lval_del(args); return lval_err(err); }

We can use this to change how our above functions are written, without actually changing what code is generated by the compiler. This makes it much easier to read for the programmer, and saves a bit of typing. The rest of the error conditions for our functions should become easy to write too!

Head & Tail

Using this our head and tail functions are defined as follows. Notice how much clearer their real functionality is.

  1. lval* builtin_head(lval* a) {
  2. LASSERT(a, a->count == 1,
  3. "Function 'head' passed too many arguments!");
  4. LASSERT(a, a->cell[0]->type == LVAL_QEXPR,
  5. "Function 'head' passed incorrect type!");
  6. LASSERT(a, a->cell[0]->count != 0,
  7. "Function 'head' passed {}!");
  8. lval* v = lval_take(a, 0);
  9. while (v->count > 1) { lval_del(lval_pop(v, 1)); }
  10. return v;
  11. }
  1. lval* builtin_tail(lval* a) {
  2. LASSERT(a, a->count == 1,
  3. "Function 'tail' passed too many arguments!");
  4. LASSERT(a, a->cell[0]->type == LVAL_QEXPR,
  5. "Function 'tail' passed incorrect type!");
  6. LASSERT(a, a->cell[0]->count != 0,
  7. "Function 'tail' passed {}!");
  8. lval* v = lval_take(a, 0);
  9. lval_del(lval_pop(v, 0));
  10. return v;
  11. }

List & Eval

The list function is simple. It just converts the input S-Expression to a Q-Expression and returns it.

The eval function is similar to the converse. It takes as input some single Q-Expression, which it converts to an S-Expression, and evaluates using lval_eval.

  1. lval* builtin_list(lval* a) {
  2. a->type = LVAL_QEXPR;
  3. return a;
  4. }
  1. lval* builtin_eval(lval* a) {
  2. LASSERT(a, a->count == 1,
  3. "Function 'eval' passed too many arguments!");
  4. LASSERT(a, a->cell[0]->type == LVAL_QEXPR,
  5. "Function 'eval' passed incorrect type!");
  6. lval* x = lval_take(a, 0);
  7. x->type = LVAL_SEXPR;
  8. return lval_eval(x);
  9. }

Join

The join function is our final function to define.

Unlike the others it can take multiple arguments, so its structure looks somewhat more like that of builtin_op. First we check that all of the arguments are Q-Expressions and then we join them together one by one. To do this we use the function lval_join. This works by repeatedly popping each item from y and adding it to x until y is empty. It then deletes y and returns x.

  1. lval* builtin_join(lval* a) {
  2. for (int i = 0; i < a->count; i++) {
  3. LASSERT(a, a->cell[i]->type == LVAL_QEXPR,
  4. "Function 'join' passed incorrect type.");
  5. }
  6. lval* x = lval_pop(a, 0);
  7. while (a->count) {
  8. x = lval_join(x, lval_pop(a, 0));
  9. }
  10. lval_del(a);
  11. return x;
  12. }
  1. lval* lval_join(lval* x, lval* y) {
  2. /* For each cell in 'y' add it to 'x' */
  3. while (y->count) {
  4. x = lval_add(x, lval_pop(y, 0));
  5. }
  6. /* Delete the empty 'y' and return 'x' */
  7. lval_del(y);
  8. return x;
  9. }