寻求可统计Lisp程序函数代码行数及递归包含函数数的工具
嘿,针对你要统计Lisp函数代码行数和递归包含函数数量的需求,我给你整理了实用的技术方案,分两部分来说:
这里有两种靠谱的思路,看你更偏向哪种场景:
基于Lisp环境的自定义实现
大多数Common Lisp实现(比如SBCL、CLISP)都提供了自省功能,可以直接获取函数的源代码。以SBCL为例,你可以写一个小函数来提取源码并统计有效行数(排除空行):;; 需要先加载split-sequence库(多数环境默认已包含) (defun count-function-lines (target-func) (let* ((source-code (sb-introspect:function-source-code target-func)) (code-string (format nil "~{~a~%~}" source-code)) (lines (split-sequence:split-sequence #\Newline code-string))) (length (remove-if #'(lambda (line) (string= (string-trim '(#\Space #\Tab #\Newline) line) "")) lines))))如果用CLISP,把
sb-introspect:function-source-code换成sys:function-lambda-expression即可,不同实现的自省API略有差异,查下对应文档就能调整。通用命令行工具
如果你不想写Lisp代码,用cloc这个通用代码统计工具就很方便。直接在命令行运行:cloc your-lisp-file.lisp它会自动区分代码行、空行、注释行,还能按函数、文件等维度给出统计结果,支持多种Lisp方言。
这里分静态分析(不运行代码)和动态分析(运行时追踪)两种方式,各有优劣:
静态分析:遍历源码收集调用关系
这种方法不需要运行程序,直接解析函数的源代码来收集所有被调用的函数(包括递归调用的)。你可以用代码遍历工具,或者自己写一个简单的遍历函数:(defvar *tracked-funcs* (make-hash-table :test #'eq)) (defun walk-code (form) (cond ((consp form) ;; 检查是否是函数调用 (when (and (symbolp (car form)) (fboundp (car form))) (setf (gethash (car form) *tracked-funcs*) t)) ;; 递归遍历所有子形式 (mapc #'walk-code form)) (t nil))) (defun count-static-included-funcs (top-func) (clrhash *tracked-funcs*) ;; 获取顶层函数的源码并遍历 (walk-code (sb-introspect:function-source-code top-func)) ;; 返回去重后的函数数量 (hash-table-count *tracked-funcs*))注意:静态分析会漏掉动态调用(比如
(funcall some-var)这种带变量的调用),如果你的代码大量使用动态调用,这种方法就不太准确。另外,处理宏的话需要先展开宏,再分析展开后的代码(可以用macroexpand-1或者macroexpand)。动态分析:运行时追踪实际调用
这种方法通过追踪函数的实际执行过程,记录所有被调用的函数,包括递归和动态调用的情况。以SBCL为例,你可以用内置的追踪功能实现:(defvar *called-funcs* (make-hash-table :test #'eq)) (defun track-call (func &rest args) (declare (ignore args)) (setf (gethash func *called-funcs*) t)) (defun count-dynamic-included-funcs (top-func &rest func-args) (clrhash *called-funcs*) ;; 设置追踪钩子,记录所有调用 (let ((trace-spec `(,top-func :entry (track-call ,top-func)))) (unwind-protect ;; 运行顶层函数 (apply top-func func-args) ;; 清理追踪 (untrace top-func))) (hash-table-count *called-funcs*))这种方法更准确,但需要确保你的代码可以安全执行(比如没有副作用或者可以控制副作用)。
另外要提醒下:不同Lisp实现的自省和追踪API可能不同,比如CLISP的追踪用trace函数,参数格式略有差异,你可以根据自己使用的环境调整代码细节。
内容的提问来源于stack exchange,提问作者user2193970

