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.
(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)
Just like QUOTE, we will define the following
abbreviations.
| Abbreviation | Equivalent 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)
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.
> `(+ 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)))
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.
> (let ((x 3) (y 5)) (+ x y)) 8 > x Symbol not bound
The LET expression clarifies the programmer's
intent to make temporary definitions.
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.
> (+ 1 2 3 4) 10
We didn't have to touch builtin_add or even recompile
the interpreter.