收集文件中的符号

Collecting the Symbols in a File

由于程序的各个文件是按顺序编译和加载的,有没有办法找出在特定文件中引用(即,实习或引用)哪些符号? (为简单起见,假设只有一个包。)

例如,如果程序的前两个文件已成功加载,如何将第二个文件中新驻留的符号以及依赖于第一个文件中定义的第二个文件的符号收集到列表?

函数 do-symbols 能否以某种方式用于提取在文件加载的每个步骤中创建的符号? (或者,是否可以独立使用 Common Lisp reader 从文件中的每种形式获取符号?)

(PS:较宽的objective是判断任意两个文件之间是否存在依赖关系。)

如果不在 reader 中做复杂的事情,您将无法轻易地找出简单引用了哪些预先存在的符号(您必须实现自己的兼容符号-reader) (但见下文)。

您可以了解创建了哪些新符号。

stash-symbols 存储符号,new-symbols returns 不在存储中的符号列表:

(defun stash-symbols (&key (packages (list-all-packages))
                           (into (make-hash-table))
                           (external-only nil))
  (dolist (p packages into)
    (if external-only
        (do-external-symbols (s p)
          (setf (gethash s into) s))
      (do-symbols (s p)
        (setf (gethash s into) s)))))

(defun new-symbols (stash &key (packages (list-all-packages))
                          (external-only nil))
  (let ((news '()))
    (dolist (p packages)
      (if external-only
          (do-external-symbols (s p)
            (unless (eq (gethash s stash) s)
              (push s news)))
          (do-symbols (s p)
            (unless (eq (gethash s stash) s)
              (push s news)))))
    news))

现在 load/comparing 进行存储、加载文件并报告新符号。请注意,加载文件可以创建包(事实上,更智能的版本会报告现有包中的新符号列表以及新包列表:这很容易做到,但我现在太懒了。

(defun load/comparing (file &key (packages nil packages-p)
                            (external-only nil))
  ;; Note the list of packages can easily change during LOAD!
  (let ((stash (stash-symbols :packages (if packages-p
                                            packages
                                          (list-all-packages))
                              :external-only external-only)))
    (values (load file)
            (new-symbols stash
                         :packages (if packages-p
                                       packages
                                     (list-all-packages))
                         :external-only external-only))))

尝试找出已加载文件中 所有 符号的一种方法是尝试编写一个函数,该函数仅假装加载文件但实际上只是读取它。这是显着很难做到的。这里有一个函数肯定不正确,但它至少有 'hear' in-package 形式,所以它可能在许多有用的情况下工作(但要注意 eval-when,并且(defpackage ...) ... (in-package ...) 会毁了它,这也许可以通过首先真正加载文件来创建包来解决:

(defun try-to-pretend-to-load-file (file)
  ;; Attempt to read a form doing what LOAD would do.  This very
  ;; definitely will not always do the right thing.
  (let ((*package* *package*))
    (with-open-file (in file)
      (loop for form = (read in nil in)
            while (not (eq form in))
            when (and (consp form)
                      (eq (first form) 'in-package))
            do (setf *package* (find-package (second form)))
            collect form))))

现在

(defun extract-interesting-symbols (forms &key (into (make-hash-table))
                                          (filter nil filterp))
  ;; Find interesting symbols in FORMS.  NIL is never interesting
  ;; (sorry).
  (labels ((extract (thing)
             (typecase thing
               (null nil)
               (symbol
                (when (or (not filterp)
                          (funcall filter thing))
                  (incf (gethash thing into 0))))
               (cons
                (extract (car thing))
                (extract (cdr thing))))))
    (extract forms)
    into))

所以,现在(使用 collecting):

> (collecting (maphash (lambda (s c) (collect (cons s c)))
                       (extract-interesting-symbols
                        (try-to-pretend-to-load-file 
                         "binding.lisp"))))
((org.tfeb.hax.binding::what . 2)
 (:compile-toplevel . 1)
 (org.tfeb.hax.binding::expanded . 4)
 (labels . 6)
 (org.tfeb.hax.binding::form/expansion . 2)
 (:load-toplevel . 1)
 (:test . 2)
 (org.tfeb.hax.binding::a . 16)
 (ecase . 1)
 (&rest . 4)
 (:org.tfeb.hax.collecting . 2)
 (org.tfeb.hax.binding::b . 20)
 (quote . 31)
 (equal . 1)
 (collecting . 1)
 (org.tfeb.hax.binding::f . 4)
 (consp . 1)
 (first . 14)
 ...)

从结果可以看出那个文件里有一个eval-when(其实也有一个defpackage),不过没关系,我已经加载了这样它的工作就完成了。