Вопрос о порядке компиляции Common Lisp - PullRequest
2 голосов
/ 21 апреля 2020

Я написал макрос и функцию в одном файле следующим образом:

(defun test ()
  (let ((x '(1 2 3)))
    (macro-test (x real-b)
      (print (+ 1 (car real-b))))))

(defmacro macro-test ((a b) &body body)
  `(do ((,b ,a (cdr ,b)))
       ((not ,b))
     ,@body))

Затем я загружаю этот файл в repl и запускаю (test). Я получил эту ошибку:

The variable REAL-B is unbound.

Однако, когда я поставил defmacro перед defun. Все хорошо.

Я запутался в общем порядке компиляции lisp. Я знаю, что если defmacro использует некоторые функции внутри, эти функции должны (eval-when (:compile-toplevel :load-toplevel :execute)), иначе компиляция не удалась бы.

Однако, если определения макросов и определения функций совпадают во время компиляции, порядок имеет значение, верно? Макрос должен находиться в том месте, где он используется (если я создаю две функции, порядок не имеет значения). Могу ли я получить более подробную информацию о порядке компиляции SBCL? И это только для SBCL? Или в стандарте Common Lisp?

Спасибо!

1 Ответ

6 голосов
/ 21 апреля 2020

Порядок всегда имеет значение: когда вы хотите использовать макрос, он должен быть известен. Макрос выполняет преобразование источника. Как бы вы смогли выполнить это преобразование исходного кода с помощью неизвестного макроса?

Стандарт Common Lisp не требует многопроходной компиляции таким образом, чтобы сначала читался весь исходный код, а все макросы собирались и затем компиляция начинается с начала файла. Компиляция файлов в Common Lisp просто просматривает исходный код от начала до конца. Позже может быть несколько этапов компиляции, но это оставлено для реализации ...

Как Lisp должен компилировать функцию test, когда макрос macro-test неизвестен? Компилятору Lisp нужно a) знать, что это макрос, и b) ему нужно иметь определение для расширения формы макроса.

Для Common Lisp это базовое c правило:

если у нас есть форма (foo bar baz), тогда оценка в основном выглядит как foo.

  1. , если foo - специальный оператор -> использовать этот специальный оператор
  2. , если foo - оператор макроса -> макрос развернуть код и начать заново
  3. , если foo - функция -> вызвать эту функцию с оцененными аргументами
  4. else -> ошибка

При компиляции это выглядит примерно так:

  1. , если foo - специальный оператор -> скомпилировать эту специальную форму
  2. , если foo - оператор макроса -> макрос развернуть форму макроса и скомпилировать этот код
  3. , если foo - функция -> скомпилировать форму функции
  4. else -> предупредить, а затем предположить, что foo является функцией, и скомпилировать вызов будущей функции с таким именем
...