bin/lispindent @ 0ab8b4c9ac2a

Update subrepository state.
author Steve Losh <steve@stevelosh.com>
date Sun, 10 Apr 2016 21:54:52 +0000
parents b69dc77f379c
children c9adf65c83ff
#!/usr/local/bin/sbcl --script

;Dorai Sitaram
;Oct 8, 1999
;last change 2014-09-16

;this script takes lines of Lisp or Scheme code from its
;stdin and produces an indented version thereof on its
;stdout

(defun safe-read (stream)
  (let ((*read-eval* nil))
    (read stream nil)))

(defun safe-read-from-string (string)
  (let ((*read-eval* nil))
    (read-from-string string)))


(defvar *lisp-keywords* '())

(defun define-with-lisp-indent-number (n syms)
  (dolist (sym syms)
    (let* ((x (symbol-name sym))
           (c (assoc x *lisp-keywords* :test #'string-equal)))
      (unless c
        (push (setq c (cons x nil)) *lisp-keywords*))
      (setf (cdr c) n))))

(define-with-lisp-indent-number 0
  '(block
    handler-bind
    loop))

(define-with-lisp-indent-number 1
  '(case
    defpackage do-all-symbols do-external-symbols dolist do-symbols dotimes
    ecase etypecase eval-when
    flet
    handler-case
    labels lambda let let* let-values
    macrolet
    prog1
    typecase
    unless unwind-protect
    when with-input-from-string with-open-file with-open-socket
    with-open-stream with-output-to-string))

(define-with-lisp-indent-number 2
  '(assert
    defun destructuring-bind do do*
    if
    multiple-value-bind
    with-slots))

(defparameter *config-files*
  (list (merge-pathnames ".lispwords" (user-homedir-pathname))
        ".lispwords" ))

(dolist (path *config-files*)
  (with-open-file (i path :if-does-not-exist nil)
    (when i
      (loop
        (let ((w (or (safe-read i) (return))))
          (define-with-lisp-indent-number (car w) (cdr w)))))))

(defun past-next-token (s i n)
  (let ((escapep nil))
    (loop
      (when (>= i n) (return i))
      (let ((c (char s i)))
        (cond (escapep (setq escapep nil))
              ((char= c #\\) (setq escapep t))
              ((char= c #\#)
               (let ((j (+ i 1)))
                 (if (>= j n) (return i)
                   (let ((c (char s j)))
                     (cond ((char= c #\\) (setq escapep t i j))
                           (t (return i)))))))
              ((member c '(#\space #\tab #\( #\) #\[ #\] #\" #\' #\` #\, #\;))
               (return i))))
      (incf i))))

(defun lisp-indent-number (s &optional (possible-keyword-p t))
  (or (cdr (assoc s *lisp-keywords* :test #'string-equal))
      (if (zerop (or (search "def" s :test #'char-equal) -1))
          0
        (if possible-keyword-p
            (let ((p (position #\: s :from-end t)))
              (if p
                  (lisp-indent-number (subseq s (1+ p)) nil)
                -1))
          -1))))

(defun literal-token-p (s)
  (let ((colon-pos (position #\: s)))
    (if colon-pos
        (if (= colon-pos 0) t nil)
      (let ((s (safe-read-from-string s)))
        (or (characterp s) (numberp s) (stringp s))))))

;(trace lisp-indent-number literal-token-p read-from-string past-next-token)

(defstruct lparen
  word
  spaces-before
  num-aligned-subforms
  (num-finished-subforms 0)
  (num-processed-subforms 0))

(defun current-word (s i n)
  (let ((j (past-next-token s i n)))
    (when (not (= i j))
      (subseq s i j))))

(defvar *labels-keywords* '(labels flet))

(defun in-labels-p (stack w)
  ; (format t "~%")
  ; (format t "WORD: ~S~%" w)
  ; (format t "STACK: ~S~%" stack)
  (let ((target (cadr stack)))
    ; (format t "TARGET: ~S~%" target)
    (and target
         (member (lparen-word target) *labels-keywords*
                 :key #'symbol-name :test #'string-equal)
         (= (lparen-num-processed-subforms target) 0))))

(defun calc-subindent (stack s i n)
  (let* ((j (past-next-token s i n))
         (num-aligned-subforms 0)
         (left-indent
          (if (= j i) 1
            (let ((w (subseq s i j)))
              (if (and (>= i 2) (member (char s (- i 2)) '(#\' #\`))) 1
                (let ((nas (if (in-labels-p stack w)
                             1
                             (lisp-indent-number w))))
                  (cond ((>= nas 0) (setq num-aligned-subforms nas)
                         2)
                        ((literal-token-p w) 1)
                        ((= j n) 2)
                        (t (+ (- j i) 2)))))))))
    (values left-indent num-aligned-subforms (1- j))))

(defun num-leading-spaces (s)
  (let ((n (length s))
        (i 0) (j 0))
    (loop
      (when (>= i n) (return 0))
      (case (char s i)
        (#\space (incf i) (incf j))
        (#\tab (incf i) (incf j 8))
        (t (return j))))))

(defun string-trim-blanks (s)
  (string-trim '(#\space #\tab #\newline #\return) s))


(defun indent-lines ()
  (let ((left-i 0) (paren-stack '()) (stringp nil))
    (loop
      (let* ((curr-line (or (read-line nil nil) (return)))
             (leading-spaces (num-leading-spaces curr-line))
             (curr-left-i
              (cond (stringp leading-spaces)
                    ((null paren-stack)
                     (when (= left-i 0) (setq left-i leading-spaces))
                     left-i)
                    (t (let* ((lp (car paren-stack))
                              (nas (lparen-num-aligned-subforms lp))
                              (nfs (lparen-num-finished-subforms lp))
                              (extra-w 0))
                         (when (< nfs nas) ;(and (>= nas 0) (< nfs nas))
                           (incf (lparen-num-finished-subforms lp))
                           (setq extra-w 2))
                         (+ (lparen-spaces-before lp)
                            extra-w))))))
        (setq curr-line (string-trim-blanks curr-line))
        (dotimes (k curr-left-i) (write-char #\space))
        ; (princ paren-stack)
        (princ curr-line) (terpri)
        ;
        (let ((i 0) (n (length curr-line)) (escapep nil)
              (inter-word-space-p nil))
          (loop
            (when (>= i n) (return))
            (let ((c (char curr-line i)))
              (cond (escapep (setq escapep nil))
                    ((char= c #\\) (setq escapep t))
                    (stringp (when (char= c #\") (setq stringp nil)))
                    ((char= c #\;) (return))
                    ((char= c #\") (setq stringp t))
                    ((member c '(#\space #\tab) :test #'char=)
                     (unless inter-word-space-p
                       (setq inter-word-space-p t)
                       (let ((lp (car paren-stack)))
                         (when lp
                           (incf (lparen-num-finished-subforms lp))))))
                    ((member c '(#\( #\[) :test #'char=)
                     (setq inter-word-space-p nil)
                     (multiple-value-bind (left-indent num-aligned-subforms j)
                         (calc-subindent paren-stack curr-line (1+ i) n)
                       (push
                        (make-lparen :word (current-word curr-line (1+ i) n)
                                     :spaces-before (+ i curr-left-i left-indent)
                                     :num-aligned-subforms num-aligned-subforms)
                        paren-stack)
                       (setq i j)))
                    ((member c '(#\) #\]) :test #'char=)
                     (setq inter-word-space-p nil)
                     (cond (paren-stack (pop paren-stack))
                           (t (setq left-i 0)))
                     (let ((lp (car paren-stack)))
                       (when lp
                         (incf (lparen-num-processed-subforms lp)))))
                    (t (setq inter-word-space-p nil)))
              (incf i))))))))

(indent-lines)

;eof