Почему расширения макроса читателей не распространяются во время выполнения (чтение)?

Почему следующее не работает?

;;;; 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 #\?

2 ответа

Решение

CL:READ отправляет на основе читаемой таблицы, привязанной к CL:*READTABLE* во время выполнения вызова READ. Под капотом 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-and-eval по-прежнему будут работать, но если вы хотите напрямую ввести синтаксис interpol в файл, этот синтаксис должен быть помещен между вызовами enable и disable. Для этой последней цели важно, чтобы вызовы Интерпола расширились до EVAL-WHEN, по той же причине, по которой необходимо, чтобы ваш запрос REQUIRE находился в пределах 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)

Потому что есть только один читатель, с глобальным состоянием. Вы эффективно включаете и выключаете свои макросы. В этом случае макросы читателя включаются только на время, read-and-eval Функция читается во время компиляции.

В этом случае вам нужно будет установить макросы в read-and-eval функция, чтобы убедиться, что читатель находится в надлежащем состоянии, когда вам это нужно.

Другие вопросы по тегам