14.3 读取宏 (Read-Macros)

7.5 节介绍过宏字符 (macro character)的概念,一个对于 read 有特别意义的字符。每一个这样的字符,都有一个相关联的函数,这函数告诉 read 当遇到这个字符时该怎么处理。你可以变更某个已存在宏字符所相关联的函数,或是自己定义新的宏字符。

函数 set-macro-character 提供了一种方式来定义读取宏 (read-macros)。它接受一个字符及一个函数,因此当 read 碰到该字符时,它返回调用传入函数后的结果。

Lisp 中最古老的读取宏之一是 ' ,即 quote 。我们可以定义成:

  1. (set-macro-character #\'
  2. #'(lambda (stream char)
  3. (list (quote quote) (read stream t nil t))))

read 在一个普通的语境下遇到 ' 时,它会返回在当前流和字符上调用这个函数的结果。(这个函数忽略了第二个参数,第二个参数永远是引用字符。)所以当 read 看到 'a 时,会返回 (quote a)

译注: read 函数接受的参数 (read &optional stream eof-error eof-value recursive)

现在我们明白了 read 最后一个参数的用途。它表示无论 read 调用是否在另一个 read 里。传给 read 的参数在几乎所有的读取宏里皆相同:传入参数有流 (stream);接着是第二个参数, t ,说明了 read 若读入的东西是 end-of-file 时,应不应该报错;第三个参数说明了不报错时要返回什么,因此在这里也就不重要了;而第四个参数 t 说明了这个 read 调用是递归的。

(译注:困惑的话可以看看 read 的定义 )

你可以(通过使用 make-dispatch-macro-character )来定义你自己的派发宏字符(dispatching macro character),但由于 # 已经是一个宏字符,所以你也可以直接使用。六个 # 打头的组合特别保留给你使用: #!#?##[##]#{#}

你可以通过调用 set-dispatch-macro-character 定义新的派发宏字符组合,与 set-macro-character 类似,除了它接受两个字符参数外。下面的代码定义了 #? 作为返回一个整数列表的读取宏。

  1. (set-dispatch-macro-character #\# #\?
  2. #'(lambda (stream char1 char2)
  3. (list 'quote
  4. (let ((lst nil))
  5. (dotimes (i (+ (read stream t nil t) 1))
  6. (push i lst))
  7. (nreverse lst)))))

现在 #?n 会被读取成一个含有整数 0n 的列表。举例来说:

  1. > #?7
  2. (1 2 3 4 5 6 7)

除了简单的宏字符,最常定义的宏字符是列表分隔符 (list delimiters)。另一个保留给用户的字符组是 #{ 。以下我们定义了一种更复杂的左括号:

  1. (set-macro-character #\} (get-macro-character #\)))
  2. (set-dispatch-macro-character #\# #\{
  3. #'(lambda (stream char1 char2)
  4. (let ((accum nil)
  5. (pair (read-delimited-list #\} stream t)))
  6. (do ((i (car pair) (+ i 1)))
  7. ((> i (cadr pair))
  8. (list 'quote (nreverse accum)))
  9. (push i accum)))))

这定义了一个这样形式 #{x y} 的表达式,使得这样的表达式被读取为所有介于 xy 之间的整数列表,包含 xy

  1. > #{2 7}
  2. (2 3 4 4 5 6 7)

函数 read-delimited-list 正是为了这样的读取宏而生的。它的第一个参数是被视为列表结束的字符。为了使 } 被识别为分隔符,必须先给它这个角色,所以程序在开始的地方调用了 set-macro-character

如果你想要在定义一个读取宏的文件里使用该读取宏,则读取宏的定义应要包在一个 eval-when 表达式里,来确保它在编译期会被求值。不然它的定义会被编译,但不会被求值,直到编译文件被载入时才会被求值。