收集文件中的符号

时间:2021-01-26 22:25:08

标签: dependencies common-lisp symbols

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

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

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

(PS:更广泛的目标是确定任意两个文件之间是否存在依赖关系。)

1 个答案:

答案 0 :(得分:2)

如果不在阅读器中做一些复杂的事情,你就不能轻易地找出哪些预先存在的符号被简单地引用(你必须实现你自己的、兼容的、符号阅读器)(但见下文)。

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

stash-symbols 生成符号存储,new-symbols 返回不在存储中的符号列表:

(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))))

尝试找出加载的文件中所有符号的一种方法是尝试编写一个函数,该函数只是假装加载文件但实际上只是读取它。这非常很难做到。这是一个绝对不能正确使用的函数,但它至少可以“听到”in-package 形式,因此它可以在许多有用的情况下工作(但要注意 eval-when,还有 {{ 1}} 会毁了它,这也许可以通过首先真正加载文件来创建包来解决):

(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))))

所以,现在(使用 collecting):

(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 (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),但没关系,因为我已经加载了它它的工作已经完成。

相关问题