Есть ли способ увидеть реализации встроенных макросов в Common Lisp? - PullRequest
2 голосов
/ 26 мая 2020

Встроенные функции Common Lisp, вероятно, реализованы в C. Но я полагаю, что макросы реализованы на лиспе (извините, если я ошибаюсь по поводу любого из двух предложений). Есть ли способ (с помощью какой-либо функции или макроса) увидеть реализации встроенных макросов в Common Lisp? Я использую CLisp.

1 Ответ

2 голосов
/ 28 мая 2020

Возможность проверять определения функций и макросов - это особенность вашей среды разработки. В наши дни типично использовать SLIME или SLY с emacs в качестве основы среды разработки Lisp. Я лично использую SLIME, но я тоже слышал хорошие отзывы о SLY.

В SLIME вы можете вызвать slime-edit-definition (либо набрав M-x slime-edit-definition, либо используя комбинацию клавиш M-.), чтобы посетить определение для символа под курсором в исходном файле. Это работает как при редактировании в исходном файле, так и в REPL. Эта функция чрезвычайно полезна, когда вы хотите проверить код некоторой библиотеки, с которой вы работаете, но вы также можете таким образом просмотреть множество встроенных определений. Вы даже можете перейти к новому определению из нового символа, найденного в любом определении, которое вы в настоящее время просматриваете.

После того, как вы закончите просмотр определения, вы можете использовать M-x slime-pop-find-definition-stack или более простую для запоминания привязку клавиш M-, (M-* также будет работать), чтобы вернуться к ранее просмотренным определениям, в конечном итоге вернувшись к исходной точке.

Вот пример в SBCL:

CL-USER> with-open-file[press M-.]

(Обратите внимание, что «[нажмите M-.]» Выше не набирается, а только для напоминания, какие действия здесь предпринимаются). Поместив курсор на символ with-open-file или сразу после него, нажмите M-., чтобы увидеть определение:

(sb-xc:defmacro with-open-file ((stream filespec &rest options)
                                &body body)
  (multiple-value-bind (forms decls) (parse-body body nil)
    (let ((abortp (gensym)))
      `(let ((,stream (open ,filespec ,@options))
             (,abortp t))
         ,@decls
         (unwind-protect
              (multiple-value-prog1
                  (progn ,@forms)
                (setq ,abortp nil))
           (when ,stream
             (close ,stream :abort ,abortp)))))))

На этот раз после нажатия M-. SLIME дает возможность выбора определений для просмотра:

CL-USER> and[press M-.]

Отображается в буфере emacs:

/path-to-source/sbcl-2.0.4/src/code/macros.lisp
  (DEFMACRO AND)
/path-to-source/sbcl-2.0.4/src/pcl/ctypes.lisp
  (DEFINE-METHOD-COMBINATION AND)

Мы хотим увидеть определение макроса, поэтому переместите курсор в строку, показывающую (DEFMACRO AND), и отобразится следующее определение:

;; AND and OR are defined in terms of IF.
(sb-xc:defmacro and (&rest forms)
  (named-let expand-forms ((nested nil) (forms forms) (ignore-last nil))
    (cond ((endp forms) t)
          ((endp (rest forms))
           (let ((car (car forms)))
             (cond (nested
                    car)
                   (t
                    ;; Preserve non-toplevelness of the form!
                    `(the t ,car)))))
          ((and ignore-last
                (endp (cddr forms)))
           (car forms))
          ;; Better code that way, since the result will only have two
          ;; values, NIL or the last form, and the precedeing tests
          ;; will only be used for jumps
          ((and (not nested) (cddr forms))
           `(if ,(expand-forms t forms t)
                ,@(last forms)))
          (t
           `(if ,(first forms)
                ,(expand-forms t (rest forms) ignore-last))))))

Здесь есть еще кое-что, так как теперь вы фактически находитесь в исходном файле, который содержит определение для and; если вы немного прокрутите вниз, вы также можете найти определение для or.

Многие функции SBCL написаны на Lisp; SBCL имеет очень качественный компилятор, поэтому многие вещи, которые в противном случае можно было бы ожидать написания на C, могут быть написаны на Lisp без потери производительности. Вот определение функции list-length:

CL-USER> list-length[press M-.]

(defun list-length (list)
  "Return the length of the given List, or Nil if the List is circular."
  (do ((n 0 (+ n 2))
       (y list (cddr y))
       (z list (cdr z)))
      (())
    (declare (type fixnum n)
             (type list y z))
    (when (endp y) (return n))
    (when (endp (cdr y)) (return (+ n 1)))
    (when (and (eq y z) (> n 0)) (return nil))))

То же самое можно сделать при использовании CLISP с SLIME. Вот with-open-file, как определено в CLISP:

CL-USER> with-open-file[press M-.]

(defmacro with-open-file ((stream &rest options) &body body)
  (multiple-value-bind (body-rest declarations) (SYSTEM::PARSE-BODY body)
    `(LET ((,stream (OPEN ,@options)))
       (DECLARE (READ-ONLY ,stream) ,@declarations)
       (UNWIND-PROTECT
         (MULTIPLE-VALUE-PROG1
           (PROGN ,@body-rest)
           ;; Why do we do a first CLOSE invocation inside the protected form?
           ;; For reliability: Because the stream may be a buffered file stream,
           ;; therefore (CLOSE ,stream) may produce a disk-full error while
           ;; writing the last block of the file. In this case, we need to erase
           ;; the file again, through a (CLOSE ,stream :ABORT T) invocation.
           (WHEN ,stream (CLOSE ,stream)))
         (WHEN ,stream (CLOSE ,stream :ABORT T))))))

Но многие функции CLISP написаны в C, и эти определения недоступны для проверки так же, как раньше:

CL-USER> list-length[press M-.]

No known definition for: list-length (in COMMON-LISP-USER)
...