首页 > 解决方案 > 有什么方法可以查看 Common Lisp 中内置宏的实现吗?

问题描述

Common Lisp 内置函数可能是用 C 实现的。但我想宏是用 lisp 实现的(对不起,如果我对两句话中的任何一个有误的话)。有没有办法(通过一些函数或一些宏)来查看 Common Lisp 中内置宏的实现?我正在使用 CLisp。

标签: macroscommon-lispclisp

解决方案


检查函数和宏定义的能力是您的开发环境的一项功能。如今,通常使用SLIMESLY和 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-.]

(请注意,上面的“[press 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)

推荐阅读