为什么以下内容不起作用?
;;;; foo.lisp
(in-package :cl-user)
(eval-when (:compile-toplevel :load-toplevel :execute)
(require :cl-interpol))
(cl-interpol:enable-interpol-syntax)
(defun read-and-eval (s)
(eval (read-from-string s)))
(cl-interpol:disable-interpol-syntax)
然后:
LISP> (load (compile-file "foo.lisp"))
=> T
LISP> (read-and-eval
"(let ((a "foo")) (princ #?"${a}"))")
=> no dispatch function defined for #?
因为只有一个读取器,具有全局状态。您有效地打开和关闭了宏。在这种情况下,读取器宏仅在编译时读取read-and-eval
函数的持续时间内启用。
在这种情况下,您需要在read-and-eval
函数中设置宏,以确保读卡器在需要时处于正确的状态。
CL:READ调度基于在READ调用运行时绑定到CL:*readtable*的readtable。ENABLE-INTERPOL-SYNTAX正在创建一个新的读表,设置CL:*readtable*来保存它,并隐藏CL:*readtable*的旧值。DISABLE-INTERPOL-SYNTAX正在取消对上一个读表的标记,并将CL:*readtable设置为再次保留它。您可以通过以下方式安排您想要的行为:
(in-package :cl-user)
(eval-when (:compile-toplevel :load-toplevel :execute)
(require :cl-interpol))
(cl-interpol:enable-interpol-syntax)
(defvar *interpol-reader* *readtable*)
(cl-interpol:disable-interpol-syntax)
(defun read-and-eval (s)
(let ((*readtable* *interpol-reader*))
(eval (read-from-string s))))
禁用语法的调用可以放在defvar之后的任何位置,read和eval仍然可以工作,但如果您想在文件中直接输入interpol语法,则该语法必须放在enable和disable调用之间。出于后一个目的,重要的是,interpol调用扩展到EVAL-WHEN,原因与您的调用需要在EVAL-WHEN内的原因相同;也就是说,当读取后一种形式时,效果需要已经发生。
CL-INTERPOL的接口抽象了正在发生的事情,所以我将向您展示如何手动创建和更改读表:
;; Create a fresh readtable with standard syntax
(defvar *not-readtable* (copy-readtable nil))
;; A simple reader function
(defun not-reader (stream char &optional count)
"Like ' but for (not ...) instead of (quote ...)"
(declare (ignore count char))
`(not ,(read stream t nil t)))
;; Mutate that readtable so that the dispatch character you want
;; calls the function you want
(set-macro-character #! 'not-reader nil *not-readtable*)
;; Try it out
(let ((*readtable* *not-readtable*))
(read-from-string "(if !foo bar baz)"))
=>
(IF (NOT FOO)
BAR
BAZ)