如何从命令行格式化 Common Lisp 代码(包括换行符)?

lan*_*anf 1 lisp code-formatting common-lisp

我正在寻找一种从命令行漂亮地打印/美化/自动格式化 Common Lisp 源代码的方法。我基本上想要一个 for python 功能的克隆black(参见https://github.com/psf/black)。它将是一个具有最小依赖性的命令行工具(例如,我不想从 emacs 内运行它),它是幂等的,可以在适当的情况下自动插入和删除换行符,以及进行缩进。

基本上,我希望能够向其提供仅包含一行的源代码,并让它生成一个可读的文件。是否存在满足所有甚至部分这些要求的东西?我已经在 github 上查看了大部分容易实现的目标,它们似乎只进行自动缩进,而不是自动格式化(无法分解长行)。如果没有,Lisp 中是否有此类事情的先例,或者由于该语言特有的某种原因,它是否异常困难?

ign*_*ens 7

[这应该是一条评论,但太长了。]

这是介于困难和不可能之间的事情。考虑以下形式,这里用一长行给出:

(with-collectors (odd even) (iterate next ((i 0)) (when (< i 100) (if (evenp i) (even i) (odd i)) (next (1+ i)))))
Run Code Online (Sandbox Code Playgroud)

这应该如何缩进?好吧,以下是一个完全了解 Lisp 的编辑器可能如何缩进它:

(with-collectors (odd even)
                 (iterate next ((i 0))
                          (when (< i 100) 
                            (if (evenp i)
                                (even i)
                              (odd i))
                            (next (1+ i)))))
Run Code Online (Sandbox Code Playgroud)

那是……大错特错了。以下是同一个编辑器稍后将缩进的方式:

(with-collectors (odd even)
  (iterate next ((i 0))
    (when (< i 100) 
      (if (evenp i)
          (even i)
        (odd i))
      (next (1+ i)))))
Run Code Online (Sandbox Code Playgroud)

这次终于做对了。

发生了什么变化?好吧,改变的是语言:特别是第二个示例中的语言已扩展为包括with-collectors编辑器现在知道如何处理的表单以及iterate它也理解的表单。

所以这看起来似乎是一个晦涩难懂的点,但事实并非如此。因为 Lisp 的全部要点(可以说)是,为了解决问题,你可以逐步、无缝地将语言从你开始使用的基础语言扩展到你想要用来解决问题的语言。

这意味着许多 Lisp 程序由一系列对该语言的扩展组成,然后是用这种新的扩展语言编写的程序,问题在其中得到解决。Lisp 是一种面向语言的编程语言

这意味着了解如何缩进 Lisp 程序的唯一真正可靠的方法是询问该程序。在上面的例子中,最初系统认为这with-collectors是一个函数,并且像这样缩进了它。后来,当它知道定义时,它意识到这是一个let-style 构造并正确地缩进了它。同样对于iterate.

所有这一切意味着,一个独立的工具实际上没有希望很好地缩进一个实质性的 Lisp 程序,因为要做到这一点,它需要比不成为程序时更多地了解该程序。当然,这就是为什么 Lisp 鼓励“驻留”开发环境,即正在开发的程序被加载到开发环境中,而不是“分离”开发环境,即开发环境或多或少与正在开发的程序完全分离。 。独立工具可能可以通过解析程序中的定义并发现扩展语言的定义来完成大部分工作。但要再次正确地做到这一点,需要你成为程序。

作为一种面向语言的编程语言带来了显着的好处,但也带来了成本,不幸的是,这就是其中之一。


如果你的任务非常有限,并且如果你真的想采取一些大的表达方式,这些表达方式都在一行上(因此,可能没有注释),那么下面将尝试执行此操作。您需要将其包装到一个程序中。

买者自负。这段代码肯定是不安全的,并且可以根据其输入执行任意代码。除非您确定输入的内容是安全的,否则请勿使用。所以,事实上,不要使用它。

;;;; Note horrid code, This is *certainly* unsafe
;;;
;;; This uses EVAL which I think is necessary here, but is what makes
;;; it unsafe.
;;;

(in-package :cl-user)

(eval-when (:compile-toplevel :load-toplevel :execute)
  (warn "UNSAFE CODE, USE AT YOUR OWN RISK."))

(defvar *tlf-handlers* (make-hash-table))

(defmacro define-tlf-handler (name ds-arglist &body forms)
  (let ((formn (make-symbol "FORM")))
    `(progn
       (setf (gethash ',name *tlf-handlers*)
             (lambda (,formn)
               (destructuring-bind ,ds-arglist (rest ,formn)
                 ,@forms)))
       ',name)))

(define-tlf-handler in-package (package)
  (let ((p (find-package package)))
    (if p
        (progn
          (format *debug-io* "~&Setting package ~S~%" package)
          (setf *package* p))
      (warn "no package ~S" package))))

(define-tlf-handler defpackage (package &body clauses)
  (format *debug-io* "~&Defining package ~S~%" package)
  (eval `(defpackage ,package ,@clauses)))

(define-tlf-handler defmacro (name arglist &body forms)
  (format *debug-io* "~&Defining macro ~S~%" name)
  (eval `(defmacro ,name ,arglist ,@forms)))

(define-tlf-handler eval-when (times &body forms)
  (declare (ignore times forms))
  (warn "Failing to handle eval-when"))

(define-condition pps-reader-error (reader-error simple-error)
  ())

(defparameter *pps-readtable* (copy-readtable nil))

(set-dispatch-macro-character
 #\# #\+
 (lambda (s c n)
   (declare (ignore c n))
   (error 'pps-reader-error
          :stream s
          :format-control "Can't handle #+"))
  *pps-readtable*)

(set-dispatch-macro-character
 #\# #\-
 (lambda (s c n)
   (declare (ignore c n))
   (error 'pps-reader-error
          :stream s
          :format-control "Can't handle #-"))
  *pps-readtable*)

(defun pp-stream (s &optional (to *standard-output*))
  (with-standard-io-syntax              ;note binds *package*
    (let ((*readtable* *pps-readtable*)
          (*read-eval* nil)
          (*print-case* :downcase))
      (do ((form (read s nil s) (read s nil s)))
          ((eq form s) (values))
        (format to "~&")
        (pprint form to)
        (when (and (consp form) (symbolp (car form)))
          (let ((handler (gethash (car form) *tlf-handlers*)))
            (when handler (funcall handler form))))))))

(defun pp-file (f &optional (to *standard-output*))
  (with-open-file (in f)
    (pp-stream in to)))
Run Code Online (Sandbox Code Playgroud)