Macros

Macros allow you to create new special forms at runtime. Unlike a function, the arguments to a macro are not evaluated. The result of evaluating the body of the macro is then itself evaluated.

Note: these are (essentially) Common LISP macros. Scheme has a different macro system, which avoids problems with identifiers introduced by the macro, but is more complex.

We will define macros using the following syntax:

(DEFMACRO (name arg...) body...)
This matches our DEFINE syntax for functions, but is slightly different from the form used in Common LISP.

Example

Take the macro IGNORE defined by:

(DEFMACRO (IGNORE X)
  (CONS 'QUOTE
    (CONS X NIL)))

If we then evaluate the expression

(IGNORE FOO)
where FOO need not be bound, the body of IGNORE will first be evaluated with the argument X bound to the unevaluated symbol FOO. The result of evaluating the nested CONS expressions within this environment is:
(QUOTE . (FOO . NIL))
which is of course equivalent to:
(QUOTE FOO)
Finally, evaluating this value (which is the result of evaluating the macro body) gives us:
FOO

Implementation

We will define a new type of atom:

AtomType_Macro
the value of which is the same as AtomType_Closure.

And now simply teach eval_expr about our new macro type.

int eval_expr(Atom expr, Atom env, Atom *result)
{
	.
	.
	.
	if (op.type == AtomType_Symbol) {
		if (strcmp(op.value.symbol, "QUOTE") == 0) {
		.
		.
		.
		} else if (strcmp(op.value.symbol, "DEFMACRO") == 0) {
			Atom name, macro;
			Error err;

			if (nilp(args) || nilp(cdr(args)))
				return Error_Args;

			if (car(args).type != AtomType_Pair)
				return Error_Syntax;

			name = car(car(args));
			if (name.type != AtomType_Symbol)
				return Error_Type;

			err = make_closure(env, cdr(car(args)),
				cdr(args), &macro);
			if (err)
				return err;

			macro.type = AtomType_Macro;
			*result = name;
			return env_set(env, name, macro);
		}
	}

	/* Evaluate operator */
	.
	.
	.

	/* Is it a macro? */
	if (op.type == AtomType_Macro) {
		Atom expansion;
		op.type = AtomType_Closure;
		err = apply(op, args, &expansion);
		if (err)
			return err;
		return eval_expr(expansion, env, result);
	}

	/* Evaulate arguments */
	.
	.
	.
}

Testing

> (defmacro (ignore x) (cons 'quote (cons x nil)))
IGNORE
> (ignore foo)
FOO
> foo
Symbol not bound

We will use macros in the future to define some new special forms.