Quasiquotation

QUASIQUOTE is an extension of the QUOTE special form which is convenient for writing macros.

For symbols and other simple data, QUASIQUOTE behaves like QUOTE, returning the datum unevaluated. Lists are also return without being evaluated, with two exceptions. If an element of the list (or a sub-list) is of the form (UNQUOTE expr), then expr is evaluated and the result inserted into the list in place. (UNQUOTE-SPLICING expr) is similar, but the result of evaluating expr must be a list, the items of which are spliced into the parent list.

Example

(QUASIQUOTE (+ 1 (UNQUOTE (+ 2 3))))
evaluates to
(+ 1 5)

If we define L to be the list (3 4 5) then

(QUASIQUOTE (1 2 (UNQUOTE-SPLICING L)))
evaluates to
(1 2 3 4 5)

Shorthand syntax

Just like QUOTE, we will define the following abbreviations.

AbbreviationEquivalent to
`expr (QUASIQUOTE expr)
,expr (UNQUOTE expr)
,@expr (UNQUOTE-SPLICING expr)

Rewriting the examples above with this syntax gives

`(+ 1 ,(+ 2 3))
and
`(1 2 ,@L)

Implementation

We extend the lexer to understand the additional special tokens.

int lex(const char *str, const char **start, const char **end)
{
	const char *ws = " \t\n";
	const char *delim = "() \t\n";
	const char *prefix = "()\'`";

	str += strspn(str, ws);

	if (str[0] == '\0') {
		*start = *end = NULL;
		return Error_Syntax;
	}

	*start = str;

	if (strchr(prefix, str[0]) != NULL)
		*end = str + 1;
	else if (str[0] == ',')
		*end = str + (str[1] == '@' ? 2 : 1);
	else
		*end = str + strcspn(str, delim);

	return Error_OK;
}

read_expr must expand the abbreviations in the same way as QUOTE

int read_expr(const char *input, const char **end, Atom *result)
{
	.
	.
	.
	if (token[0] == '(') {
	.
	.
	.
	} else if (token[0] == '`') {
		*result = cons(make_sym("QUASIQUOTE"), cons(nil, nil));
		return read_expr(*end, end, &car(cdr(*result)));
	} else if (token[0] == ',') {
		*result = cons(make_sym(
			token[1] == '@' ? "UNQUOTE-SPLICING" : "UNQUOTE"),
			cons(nil, nil));
		return read_expr(*end, end, &car(cdr(*result)));
	} else {
		.
		.
		.
	}
}

The QUASIQUOTE operator itself may be defined as a macro. First we need a few helper functions.

(define (append a b) (foldr cons b a))

(define (caar x) (car (car x)))

(define (cadr x) (car (cdr x)))

(append a b) concatenates the lists a and b.

And now the macro itself:

(defmacro (quasiquote x)
  (if (pair? x)
      (if (eq? (car x) 'unquote)
          (cadr x)
          (if (eq? (if (pair? (car x)) (caar x) nil) 'unquote-splicing)
              (list 'append
                    (cadr (car x))
                    (list 'quasiquote (cdr x)))
              (list 'cons
                    (list 'quasiquote (car x))
                    (list 'quasiquote (cdr x)))))
      (list 'quote x)))

The definition above is a little hard to follow, since the resulting expression must be built up using LIST and may include additional calls to QUASIQUOTE.

Quasiquotation allows us to make the body of a macro look like the expression it returns; for example the IGNORE macro in chapter 11

(DEFMACRO (IGNORE X)
  (CONS 'QUOTE (CONS X NIL)))
can now be written
(DEFMACRO (IGNORE X)
  `(QUOTE ,X))
and the operation is made clear.

Testing

> `(+ 1 ,(+ 2 3))
(+ 1 5)
> (define l '(3 4 5))
L
> `(1 2 ,@l)
(1 2 3 4 5)

let

We will now use QUASIQUOTE to define a new special form:

(LET ((sym1 expr1)
      (sym2 expr2)
      ...)
  body...)

LET causes the expressions expr to be evaluated with the symbols sym1, sym2... bound to the result of evaluating expr1, expr2 and so on. The result of the last expression body to be evaluated is returned.

The definition is simple.

(defmacro (let defs . body)
  `((lambda ,(map car defs) ,@body)
    ,@(map cadr defs)))

Example

When we evaluate the form

(LET ((X 3) (Y 5)) (+ X Y))
it is transformed by the LET macro into
((LAMBDA (X Y) (+ X Y)) 3 5)
which behaves as desired.

Testing

> (let ((x 3) (y 5)) (+ x y))
8
> x
Symbol not bound

The LET expression clarifies the programmer's intent to make temporary definitions.

A trick

We can use LET to extend the built-in binary operator + to accept any number of arguments.

(define +
  (let ((old+ +))
    (lambda xs (foldl old+ 0 xs))))

Compare this with the definition of ADD add the end of chapter 10.

Testing

> (+ 1 2 3 4)
10

We didn't have to touch builtin_add or even recompile the interpreter.