├── .pre-commit-hooks.yaml ├── Makefile ├── README.md ├── lisp-format └── test ├── .gitignore ├── add-tabs-default ├── args ├── check └── example.lisp ├── drop-tabs ├── .lisp-format ├── args ├── check └── example.lisp ├── fix-trailing-parens ├── .lisp-format ├── args ├── check └── example.lisp ├── hangs ├── .lisp-format ├── args ├── check ├── delta-debug-run └── example.lisp └── not-add-tabs ├── .lisp-format ├── args ├── check ├── delta-debug-run ├── delta-debug-run-simple └── example.lisp /.pre-commit-hooks.yaml: -------------------------------------------------------------------------------- 1 | - id: lisp-format 2 | name: formatter of lisp code 3 | description: Run lisp-format against lisp files 4 | language: script 5 | files: \.(lisp|cl|asd|scm|el)$ 6 | entry: lisp-format -i 7 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | SHELL=bash 2 | 3 | TEST_DIR=test 4 | TESTS= \ 5 | drop-tabs \ 6 | not-add-tabs \ 7 | add-tabs-default \ 8 | fix-trailing-parens 9 | 10 | all: check 11 | 12 | PASS=\e[1;1m\e[1;32mPASS\e[1;0m 13 | FAIL=\e[1;1m\e[1;31mFAIL\e[1;0m 14 | check/%: $(TEST_DIR)/% 15 | @cd $^; ../../lisp-format $$(cat args 2>/dev/null) example.lisp > output.lisp; \ 16 | if ./check >/dev/null 2>/dev/null;then \ 17 | printf "$(PASS)\t\e[1;1m%s\e[1;0m\n" $*; exit 0; \ 18 | else \ 19 | printf "$(FAIL)\t\e[1;1m%s\e[1;0m\n" $*; exit 1; \ 20 | fi 21 | 22 | check: $(addprefix check/, $(TESTS)) 23 | 24 | show/%: $(TEST_DIR)/% 25 | -@diff -U4 --color=always $^/{example,output}.lisp|sed '4,$$ { s/\t/ [TAB] /g}' 26 | 27 | clean: 28 | rm -f test/*/output.lisp 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Lisp-Format --- A tool to format lisp code. 2 | 3 | This lisp-format script aims to provide the same functionality as 4 | clang-format only for lisp languages instead of for C languages. 5 | Emacs is used to perform formatting. The behavior of clang-format is 6 | followed as closely as possible with the goal of a direct drop in 7 | replacement in most contexts. 8 | 9 | This script is suitable for (re)formatting lisp code using an external 10 | process. This script may be marked as executable and executed 11 | directly on the command line as follows. 12 | 13 | ```shell 14 | chmod +x lisp-format 15 | ./lisp-format -h 16 | ``` 17 | 18 | This script is appropriate for use in git commit hooks, see 19 | [Running lisp-format git hooks](#running-lisp-format-git-hooks). 20 | 21 | ## Customizing lisp-format 22 | 23 | Clang-format allows for customized "styles" to be specified by 24 | writing .clang-format files in the base of code repository and 25 | passing the -style=file flag to clang-format. Lisp-format 26 | supports the same using .lisp-format files. These files may hold 27 | arbitrary emacs-lisp code, and they will be loaded before every 28 | run of lisp-format. The following example file will (1) load 29 | slime assuming you have slime installed via quicklisp, (2) adjust 30 | the syntax-table for special reader macro characters, and (3) 31 | specify indentation for non-standard functions (e.g., from the 32 | popular common lisp Alexandria package). 33 | 34 | ```lisp 35 | ;;;; -*- emacs-lisp -*- 36 | (set-default 'indent-tabs-mode nil) 37 | (mapc (lambda (dir) (add-to-list 'load-path dir)) 38 | (directory-files 39 | (concat (getenv "QUICK_LISP") 40 | "/dists/quicklisp/software/") t "slime-v*")) 41 | (require 'slime) 42 | 43 | ;;; Syntax table extension for curry-compose-reader-macros 44 | (modify-syntax-entry ?\[ "(]" lisp-mode-syntax-table) 45 | (modify-syntax-entry ?\] ")[" lisp-mode-syntax-table) 46 | (modify-syntax-entry ?\{ "(}" lisp-mode-syntax-table) 47 | (modify-syntax-entry ?\} "){" lisp-mode-syntax-table) 48 | (modify-syntax-entry ?\« "(»" lisp-mode-syntax-table) 49 | (modify-syntax-entry ?\» ")«" lisp-mode-syntax-table) 50 | 51 | ;;; Specify indentation levels for specific functions. 52 | (mapc (lambda (pair) (put (first pair) 'lisp-indent-function (second pair))) 53 | '((if-let 1) 54 | (if-let* 1))) 55 | ``` 56 | 57 | As described in the "git lisp-format -h" output, you can use "git 58 | config" to change the default style to "file" with the following 59 | command (run in a git repository). 60 | 61 | ```shell 62 | git config lispFormat.style "file" 63 | ``` 64 | 65 | Running the above and adding a `.lisp-format` file to the base of a 66 | git repository enables customization of the lisp-format behavior. 67 | 68 | Currently lisp-format only exports a single configuration variable 69 | which may be customized to run fixers on formatted regions of code. 70 | See the documentation of `*LISP-FORMAT-FIXERS**` for details. For 71 | example to remove all tabs add the following: 72 | 73 | ```lisp 74 | ;; NOTE: unless indent-tabs-mode is `set-default' to nil 75 | ;; subsequent fixers after untabify could themselves add 76 | ;; tabs. 77 | (set-default 'indent-tabs-mode nil) 78 | (push 'untabify *lisp-format-fixers*) 79 | ``` 80 | 81 | ## User-specific customization 82 | 83 | User-specific customization of lisp-format may be accomplished by 84 | writing emacs-lisp code to an configuration file named 85 | `~/.lisp-formatrc` in the users home directory. This may be useful 86 | for adding directories to the load path searched by lisp-format. 87 | 88 | 89 | ## Running lisp-format git hooks 90 | 91 | The lisp-format script is appropriate for use in git commit hooks. In 92 | fact the 93 | [git-clang-format script](https://raw.githubusercontent.com/llvm-mirror/clang/master/tools/clang-format/git-clang-format) 94 | may be fairly trivially converted into a git-lisp-format script by 95 | replacing "clang" with "lisp" and changing the in-scope file 96 | extensions as follows. 97 | 98 | ```shell 99 | curl -s https://raw.githubusercontent.com/llvm-mirror/clang/master/tools/clang-format/git-clang-format \ 100 | |sed \ 101 | "s/clang-format/lisp-format/g;s/clangFormat/lispFormat/; 102 | s/default_extensions =.*\$/default_extensions = ','.join(['lisp','cl','asd','scm','el'])/; 103 | /# From clang\/lib\/Frontend\/FrontendOptions.cpp, all lower case/,/])/d" \ 104 | > /usr/bin/git-lisp-format 105 | 106 | chmod +x /usr/bin/git-lisp-format 107 | ``` 108 | 109 | After the resulting git-lisp-format is added to your path then git can 110 | execute this file by running "git lisp-format." 111 | 112 | See 113 | [setting up git clang format](https://dx13.co.uk/articles/2015/04/03/setting-up-git-clang-format/) 114 | for an example description of a work flow leveraging git hooks and 115 | git-clang-format to ensure that code is well formatted before every 116 | commit (i.e., by writing the following shell code to an executable 117 | file named `pre-commit` in a repository's `.git/hooks/` directory). 118 | This work flow may be trivially adopted to use git-lisp-format for 119 | lispy code. 120 | 121 | It is relatively easy to configure git to run lisp-format before every 122 | commit, and reject commits which are not well formatted. The 123 | following two subsections describe how to do this using a [Pre-commit 124 | hook shell script](#pre-commit-hook-shell-script) or using the 125 | [Pre-commit framework](#pre-commit-framework). 126 | 127 | ### Pre-commit hook shell script 128 | 129 | ```shell 130 | #!/bin/bash 131 | # pre-commit shell script to run git-lisp-format. 132 | OUTPUT=$(git lisp-format --diff) 133 | if [ "${OUTPUT}" == "no modified files to format" ] || 134 | [ "${OUTPUT}" == "lisp-format did not modify any files" ];then 135 | exit 0 136 | else 137 | echo "Run git lisp-format, then commit." 138 | exit 1 139 | fi 140 | ``` 141 | 142 | ### Pre-commit framework 143 | 144 | The `.pre-commit-hooks.yaml` file in this directory provides a 145 | [pre-commit framework](https://pre-commit.com/) hook for running 146 | lisp-format. This hook requires the pre-commit framework to be 147 | installed on your system and configured for each git repository. For 148 | more information please refer to: 149 | - [pre-commit installation](https://pre-commit.com/#install) 150 | - [pre-commit plugins](https://pre-commit.com/#plugins) (`.pre-commit-config.yaml`) 151 | - [pre-commit usage](https://pre-commit.com/#usage) 152 | - [pre-commit hook list](https://pre-commit.com/hooks.html) 153 | -------------------------------------------------------------------------------- /lisp-format: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | :; exec emacs -Q --script "$0" -- "$@" # -*- emacs-lisp -*- 3 | ;;; lisp-format --- A tool to format lisp code. Designed to mimic clang-format. 4 | 5 | ;;; Author: Eric Schulte 6 | ;;; Copyright (c) 2018 Eric Schulte 7 | ;;; MIT License 8 | 9 | ;;; Permission is hereby granted, free of charge, to any person 10 | ;;; obtaining a copy of this software and associated documentation 11 | ;;; files (the "Software"), to deal in the Software without 12 | ;;; restriction, including without limitation the rights to use, copy, 13 | ;;; modify, merge, publish, distribute, sublicense, and/or sell copies 14 | ;;; of the Software, and to permit persons to whom the Software is 15 | ;;; furnished to do so, subject to the following conditions: 16 | ;;; 17 | ;;; The above copyright notice and this permission notice shall be 18 | ;;; included in all copies or substantial portions of the Software. 19 | ;;; 20 | ;;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 21 | ;;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 22 | ;;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 23 | ;;; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 24 | ;;; HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 25 | ;;; WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | ;;; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 27 | ;;; DEALINGS IN THE SOFTWARE. 28 | 29 | ;;; Commentary: 30 | ;;; 31 | ;;; This lisp-format script aims to provide the same functionality as 32 | ;;; clang-format only for lisp languages instead of for C languages. 33 | ;;; Emacs is used to perform formatting. The behavior of clang-format 34 | ;;; is followed as closely as possible with the goal of a direct drop 35 | ;;; in replacement in most contexts. 36 | ;;; 37 | ;;; This script is suitable for (re)formatting lisp code using an 38 | ;;; external process. This script may be marked as executable and 39 | ;;; executed directly on the command line as follows. 40 | ;;; 41 | ;;; chmod +x lisp-format 42 | ;;; ./lisp-format -h 43 | ;;; 44 | ;;; This script is appropriate for use in git commit hooks. In fact 45 | ;;; the git-clang-format script [1] may be fairly trivially converted 46 | ;;; into a git-lisp-format script by replacing "clang" with "lisp" and 47 | ;;; changing the in-scope file extensions as follows. 48 | ;;; 49 | ;;; curl https://raw.githubusercontent.com/llvm-mirror/clang/master/tools/clang-format/git-clang-format \ 50 | ;;; |sed \ 51 | ;;; "s/clang-format/lisp-format/g;s/clangFormat/lispFormat/; 52 | ;;; s/default_extensions =.*\$/default_extensions = ','.join(['lisp','cl','asd','scm','el'])/; 53 | ;;; /# From clang\/lib\/Frontend\/FrontendOptions.cpp, all lower case/,/])/d" \ 54 | ;;; > /usr/bin/git-lisp-format 55 | ;;; 56 | ;;; chmod +x /usr/bin/git-lisp-format 57 | ;;; 58 | ;;; After the resulting git-lisp-format is added to your path then git 59 | ;;; can execute this file by running "git lisp-format." 60 | ;;; 61 | ;;; See [2] for an example description of a work flow leveraging git 62 | ;;; hooks and git-clang-format to ensure that code is well formatted 63 | ;;; before every commit (i.e., by adding [3] to an executable file 64 | ;;; named pre-commit in a repository's .git/hooks/ directory). This 65 | ;;; work flow may be trivially adopted to use git-lisp-format for 66 | ;;; lispy code. 67 | ;;; 68 | ;;; Clang-format allows for customized "styles" to be specified by 69 | ;;; writing .clang-format files in the base of code repository and 70 | ;;; passing the -style=file flag to clang-format. Lisp-format 71 | ;;; supports the same using .lisp-format files. These files may hold 72 | ;;; arbitrary emacs-lisp code, and they will be loaded before every 73 | ;;; run of lisp-format. The following example file will (1) load 74 | ;;; slime assuming you have slime installed via quicklisp, (2) adjust 75 | ;;; the syntax-table for special reader macro characters, and (3) 76 | ;;; specify indentation for non-standard functions (e.g., from the 77 | ;;; popular common lisp Alexandria package). 78 | ;;; 79 | ;;; ;;;; -*- emacs-lisp -*- 80 | ;;; (setq *lisp-format-tab-control* :never) 81 | ;;; (mapc (lambda (dir) (add-to-list 'load-path dir)) 82 | ;;; (directory-files 83 | ;;; (concat (getenv "QUICK_LISP") 84 | ;;; "/dists/quicklisp/software/") t "slime-v*")) 85 | ;;; (require 'slime) 86 | ;;; 87 | ;;; ;;; Syntax table extension for curry-compose-reader-macros 88 | ;;; (modify-syntax-entry ?\[ "(]" lisp-mode-syntax-table) 89 | ;;; (modify-syntax-entry ?\] ")[" lisp-mode-syntax-table) 90 | ;;; (modify-syntax-entry ?\{ "(}" lisp-mode-syntax-table) 91 | ;;; (modify-syntax-entry ?\} "){" lisp-mode-syntax-table) 92 | ;;; (modify-syntax-entry ?\« "(»" lisp-mode-syntax-table) 93 | ;;; (modify-syntax-entry ?\» ")«" lisp-mode-syntax-table) 94 | ;;; 95 | ;;; ;;; Specify indentation levels for specific functions. 96 | ;;; (mapc (lambda (pair) (put (first pair) 'lisp-indent-function (second pair))) 97 | ;;; '((if-let 1) 98 | ;;; (if-let* 1))) 99 | ;;; 100 | ;;; As described in the "git lisp-format -h" output, you can use "git 101 | ;;; config" to change the default style to "file" with the following 102 | ;;; command (run in a git repository). 103 | ;;; 104 | ;;; git config lispFormat.style "file" 105 | ;;; 106 | ;;; Running the above and adding a .lisp-format file to the based of a 107 | ;;; git repository enables customization of the lisp-format behavior. 108 | ;;; 109 | ;;; Currently lisp-format only exports a single configuration variable 110 | ;;; which may be customized to run fixers on formatted regions of 111 | ;;; code. See the documentation of `*LISP-FORMAT-FIXERS**` for 112 | ;;; details. For example to remove all tabs add the following: 113 | ;;; 114 | ;;; ;; NOTE: unless indent-tabs-mode is `set-default' to nil 115 | ;;; ;; subsequent fixers after untabify could themselves add 116 | ;;; ;; tabs. 117 | ;;; (set-default 'indent-tabs-mode nil) 118 | ;;; (push 'untabify *lisp-format-fixers*) 119 | ;;; 120 | ;;; User-specific customization of lisp-format may be accomplished by 121 | ;;; writing emacs-lisp code to an configuration file named 122 | ;;; "~/.lisp-formatrc" in the users home directory. This may be 123 | ;;; useful for adding directories to the load path searched by 124 | ;;; lisp-format. 125 | ;;; 126 | ;;; [1] https://raw.githubusercontent.com/llvm-mirror/clang/master/tools/clang-format/git-clang-format 127 | ;;; [2] https://dx13.co.uk/articles/2015/4/3/Setting-up-git-clang-format.html 128 | ;;; [3] .git/hooks/pre-commit 129 | ;;; 130 | ;;; #!/bin/bash 131 | ;;; OUTPUT=$(git lisp-format --diff) 132 | ;;; if [ "${OUTPUT}" == "no modified files to format" ] || 133 | ;;; [ "${OUTPUT}" == "lisp-format did not modify any files" ];then 134 | ;;; exit 0 135 | ;;; else 136 | ;;; echo "Run git lisp-format, then commit." 137 | ;;; exit 1 138 | ;;; fi 139 | ;;; 140 | (require 'cl-lib) 141 | 142 | 143 | ;;; Variables and functions 144 | (defvar inplace nil) 145 | (defvar offsets nil) 146 | (defvar lengths nil) 147 | (defvar lines nil) 148 | (defvar style :default) 149 | (defvar verbose nil) 150 | 151 | (defvar *lisp-format-fixers* 152 | '(indent-region delete-trailing-whitespace) 153 | "User-configurable list of \"fixers\" used to format regions. 154 | By default only indentation and trailing whitespace removal are 155 | applied. The following customization of this variable will 156 | remove all tabs: 157 | 158 | ;; NOTE: unless indent-tabs-mode is `set-default' to nil 159 | ;; subsequent fixers after untabify could themselves add 160 | ;; tabs. 161 | (set-default 'indent-tabs-mode nil) 162 | (push 'untabify *lisp-format-fixers*) 163 | 164 | and the following will cause lisp-format to fix any trailing 165 | parenthesis (using paredit). 166 | 167 | (defun fix-trailing-parens (start end &optional _arg) 168 | \"Use `paredit-close-parenthesis' to fix trailing parens.\" 169 | (interactive (if current-prefix-arg 170 | (list (point-min) (point-max) current-prefix-arg) 171 | (list (region-beginning) (region-end) nil))) 172 | (let ((c (current-column))) 173 | (save-excursion 174 | (save-restriction 175 | (narrow-to-region (point-min) end) 176 | (goto-char start) 177 | (while (re-search-forward \"^ *)\" nil t) 178 | (forward-char -1) 179 | (paredit-close-parenthesis)))) 180 | (move-to-column c))) 181 | (push 'fix-trailing-parens *lisp-format-fixers*)") 182 | 183 | (defun starts-with-p (substring string) 184 | (let ((sub-length (length substring))) 185 | (and (>= (length string) sub-length) 186 | (string= (cl-subseq string 0 sub-length) substring)))) 187 | 188 | (defun abort (code fmt &rest args) 189 | (apply #'message fmt args) 190 | ;; (message "EXIT:%S" code) 191 | (kill-emacs 2)) 192 | 193 | (defmacro getopts (&rest forms) 194 | "Collect command-line options from ARGS in an executable." 195 | (let ((arg (gensym)) 196 | (getopts-block (gensym))) 197 | `(cl-block ,getopts-block 198 | (cl-loop for ,arg = (pop argv) while ,arg do 199 | (cond 200 | ,@(mapcar (lambda (form) 201 | `((starts-with-p ,(car form) ,arg) 202 | (when (and (> (length ,arg) ,(length (car form))) 203 | (= ?\= (aref ,arg ,(length (car form))))) 204 | (push (cl-subseq ,arg (1+ ,(length (car form)))) 205 | argv)) 206 | ,@(cdr form))) 207 | forms) 208 | ((starts-with-p "-" ,arg) 209 | (abort 2 (concat 210 | "lisp-format: Unknown command line argument '%s'." 211 | " Try: 'lisp-format -help'") ,arg)) 212 | (t (push ,arg argv) (cl-return-from ,getopts-block))))))) 213 | 214 | (defun find-file-recursively (file directory) 215 | (let ((current (expand-file-name file directory))) 216 | (if (file-exists-p current) 217 | current 218 | (let ((parent-directory 219 | (file-name-directory (directory-file-name directory)))) 220 | (if (string= "/" parent-directory) 221 | nil 222 | (find-file-recursively file parent-directory)))))) 223 | 224 | (defun show-help () 225 | (abort 0 "OVERVIEW: A tool to format common-lisp/emacs-lisp/scheme code. 226 | 227 | If no arguments are specified, it formats the code from standard input 228 | and writes the result to the standard output. 229 | If s are given, it reformats the files. If -i is specified 230 | together with s, the files are edited in-place. Otherwise, the 231 | result is written to the standard output. 232 | 233 | USAGE: lisp-format [options] [ ...] 234 | 235 | OPTIONS: 236 | -verbose - Turn on verbose output. 237 | -assume-filename= - When reading from stdin, lisp-format assumes this 238 | filename to look for a style config file (with 239 | -style=file) and to determine the language. 240 | -fallback-style= - The name of the predefined style used as a 241 | fallback in case lisp-format is invoked with 242 | -style=file, but can not find the .lisp-format 243 | file to use. 244 | Use -fallback-style=none to skip formatting. 245 | -help - Display available options (-help-hidden for more) 246 | -i - Inplace edit s, if specified. 247 | -length= - Format a range of this length (in bytes). 248 | Multiple ranges can be formatted by specifying 249 | several -offset and -length pairs. 250 | When only a single -offset is specified without 251 | -length, lisp-format will format up to the end 252 | of the file. 253 | Can only be used with one input file. 254 | -lines= - : - format a range of 255 | lines (both 1-based). 256 | Multiple ranges can be formatted by specifying 257 | several -lines arguments. 258 | Can't be used with -offset and -length. 259 | Can only be used with one input file. 260 | -offset= - Format a range starting at this byte offset. 261 | Multiple ranges can be formatted by specifying 262 | several -offset and -length pairs. 263 | Can only be used with one input file. 264 | -style= - Coding style, currently supports: 265 | LLVM, Google, Chromium, Mozilla, WebKit. 266 | Use -style=file to load style configuration from 267 | .lisp-format file located in one of the parent 268 | directories of the source file (or current 269 | directory for stdin). 270 | Use -style=\"{key: value, ...}\" to set specific 271 | parameters, e.g.: 272 | -style=\"{BasedOnStyle: llvm, IndentWidth: 8}\" 273 | DEFERRED OPTIONS (maybe one day): 274 | -output-replacements-xml - Output replacements as XML. 275 | -sort-includes - If set, overrides the include sorting behavior 276 | determined by the SortIncludes style flag 277 | -cursor= - The position of the cursor when invoking 278 | lisp-format from an editor integration 279 | -dump-config - Dump configuration options to stdout and exit. 280 | Can be used with -style option. 281 | -version - Display the version of this program")) 282 | 283 | (defvar *debug-out-counter* 0) 284 | (defun debug-out () 285 | (write-region (point-min) (point-max) 286 | (format "/tmp/lisp-format-debug-%03d" *debug-out-counter*)) 287 | (cl-incf *debug-out-counter*)) 288 | 289 | (defun lisp-format-process-file (file) 290 | (unless (file-exists-p file) 291 | (abort "No such file or directory: '%s'" file)) 292 | ;; 0. Read the style file if one is present. 293 | (when (eql style :file) 294 | (let ((lisp-format-file 295 | (find-file-recursively 296 | ".lisp-format" (file-name-directory (expand-file-name file))))) 297 | (if lisp-format-file 298 | (let ((inhibit-message (if verbose nil t))) 299 | (load lisp-format-file nil t t)) 300 | (error "No '.lisp-format' file found in '%s' or parent directories." 301 | (file-name-directory (expand-file-name file)))))) 302 | ;; In a new buffer visiting the file: 303 | (save-window-excursion 304 | (find-file file) 305 | (let (point-ranges) 306 | ;; Figure out the regions in terms of points (offsets and lengths). 307 | (if (or offsets lengths lines) 308 | (progn 309 | (cl-loop for offset in offsets ; From offsets and lengths. 310 | for length in lengths 311 | do (push (cons offset (+ offset length)) point-ranges)) 312 | (mapc (lambda (range) ; From lines. 313 | (cl-destructuring-bind (start end) range 314 | (push (cons (point-at-bol start) (point-at-eol end)) 315 | point-ranges))) 316 | lines)) 317 | (setf point-ranges (list (cons (point-min) (point-max))))) 318 | (setf point-ranges (sort point-ranges (lambda (a b) (> (car a) (car b))))) 319 | ;; Format all point ranges. 320 | ;; (debug-out) 321 | (mapc (lambda (pair) 322 | (cl-destructuring-bind (start . end) pair 323 | (mapc (lambda (fixer) 324 | (let ((inhibit-message (if verbose nil t))) 325 | (funcall fixer start (min end (point-max))) 326 | ;; (debug-out) 327 | )) 328 | *lisp-format-fixers*))) 329 | point-ranges)) 330 | ;; (debug-out) 331 | (if inplace 332 | (basic-save-buffer nil) 333 | (princ (buffer-string))))) 334 | 335 | 336 | ;;;; Actual runtime code 337 | 338 | ;;; Load a global .lisp-formatrc file when present. 339 | (let ((lisp-format-rc-file "~/.lisp-formatrc")) 340 | (when (file-exists-p lisp-format-rc-file) 341 | (let ((inhibit-message (if verbose nil t))) 342 | (load lisp-format-rc-file nil t t)))) 343 | 344 | ;;; (message "Command-line arguments:%S" argv) 345 | (pop argv) ; Remove the leading "--". 346 | 347 | (getopts 348 | ("-h" (show-help)) 349 | ("--help" (show-help)) 350 | ("-verbose" (progn (setf verbose t) (setf debug-on-error t))) 351 | ("-i" (setf inplace t)) 352 | ("-length" (push (string-to-number (pop argv)) lengths)) 353 | ("-lines" (push (mapcar #'string-to-number (split-string (pop argv) ":")) 354 | lines)) 355 | ("-offset" (push (string-to-number (pop argv)) offsets)) 356 | ("-style" (setf style (intern (concat ":" (pop argv)))))) 357 | 358 | ;;; Process every file specified on the command line. 359 | (cl-do ((file (pop argv) (pop argv))) 360 | ((null file)) 361 | (lisp-format-process-file file)) 362 | -------------------------------------------------------------------------------- /test/.gitignore: -------------------------------------------------------------------------------- 1 | **/output.lisp 2 | **/original.lisp 3 | **/minimized.lisp 4 | -------------------------------------------------------------------------------- /test/add-tabs-default/args: -------------------------------------------------------------------------------- 1 | -lines=0:7 -------------------------------------------------------------------------------- /test/add-tabs-default/check: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Ensure output has no tabs. 3 | if [ ! -f output.lisp ];then 4 | echo "No output produced!" 5 | exit 1 6 | else 7 | # The 'grep -v' drops the line that had tabs in the original. 8 | if $(grep -v ":object variant" output.lisp|grep -qP "\t");then 9 | echo "Indented with tabs in in output.lisp." 10 | exit 0 11 | else 12 | echo "Indented with spaces." 13 | exit 1 14 | fi 15 | fi 16 | -------------------------------------------------------------------------------- /test/add-tabs-default/example.lisp: -------------------------------------------------------------------------------- 1 | (deftest homologous-crossover-with-cut () 2 | (with-fixture gcd-asm 3 | (let ((variant (copy *gcd*)) 4 | (target 40)) 5 | ;; apply cut to variant 6 | (apply-mutation variant 7 | (make-instance 'simple-cut 8 | :object variant 9 | -------------------------------------------------------------------------------- /test/drop-tabs/.lisp-format: -------------------------------------------------------------------------------- 1 | ;;;; -*- emacs-lisp -*- 2 | (pushnew #'untabify *lisp-format-fixers*) 3 | -------------------------------------------------------------------------------- /test/drop-tabs/args: -------------------------------------------------------------------------------- 1 | -style=file 2 | -------------------------------------------------------------------------------- /test/drop-tabs/check: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Ensure output has no tabs. 3 | if [ ! -f output.lisp ];then 4 | echo "No output produced!" 5 | exit 1 6 | else 7 | if $(grep -qP "\t" output.lisp);then 8 | echo "Tab remains in output.lisp." 9 | exit 1 10 | else 11 | echo "Success." 12 | exit 0 13 | fi 14 | fi 15 | -------------------------------------------------------------------------------- /test/drop-tabs/example.lisp: -------------------------------------------------------------------------------- 1 | (defun method-with-tabs () 2 | #|...This line starts with a TAB character....|#) 3 | -------------------------------------------------------------------------------- /test/fix-trailing-parens/.lisp-format: -------------------------------------------------------------------------------- 1 | (require 'paredit) 2 | (defun fix-trailing-parens (start end &optional _arg) 3 | "Use `paredit-close-parenthesis' to fix trailing parens." 4 | (interactive (if current-prefix-arg 5 | (list (point-min) (point-max) current-prefix-arg) 6 | (list (region-beginning) (region-end) nil))) 7 | (let ((c (current-column))) 8 | (save-excursion 9 | (save-restriction 10 | (narrow-to-region (point-min) end) 11 | (goto-char start) 12 | (while (re-search-forward "^ *)" nil t) 13 | (forward-char -1) 14 | (paredit-close-parenthesis)))) 15 | (move-to-column c))) 16 | (pushnew 'fix-trailing-parens *lisp-format-fixers*) 17 | -------------------------------------------------------------------------------- /test/fix-trailing-parens/args: -------------------------------------------------------------------------------- 1 | -style=file -------------------------------------------------------------------------------- /test/fix-trailing-parens/check: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Ensure output has no tabs. 3 | if [ ! -f output.lisp ];then 4 | echo "No output produced!" 5 | exit 1 6 | else 7 | # The 'grep -v' drops the line that had tabs in the original. 8 | if $(grep -qP "^[[:space:]]\)" output.lisp);then 9 | echo "Trailing parens in output.lisp." 10 | exit 1 11 | else 12 | echo "No trailing parens in output.lisp." 13 | exit 0 14 | fi 15 | fi 16 | -------------------------------------------------------------------------------- /test/fix-trailing-parens/example.lisp: -------------------------------------------------------------------------------- 1 | (deftest homologous-crossover-with-cut () 2 | (with-fixture gcd-asm 3 | (let ((variant (copy *gcd*)) 4 | (target 40) 5 | ) 6 | ;; apply cut to variant 7 | (apply-mutation variant 8 | (make-instance 'simple-cut 9 | :object variant)) 10 | ))) 11 | -------------------------------------------------------------------------------- /test/hangs/.lisp-format: -------------------------------------------------------------------------------- 1 | ;;;; -*- emacs-lisp -*- 2 | (mapc (lambda (dir) (add-to-list 'load-path dir)) 3 | (directory-files (concat (getenv "QUICK_LISP") 4 | "/dists/quicklisp/software/") t "slime-v*")) 5 | (require 'slime) 6 | (require 'paredit) 7 | 8 | (set-default 'indent-tabs-mode nil) 9 | (pushnew 'untabify *lisp-format-fixers*) 10 | 11 | (defun fix-trailing-parens (start end &optional _arg) 12 | "Use `paredit-close-parenthesis' to fix trailing parens." 13 | (interactive (if current-prefix-arg 14 | (list (point-min) (point-max) current-prefix-arg) 15 | (list (region-beginning) (region-end) nil))) 16 | (let ((c (current-column))) 17 | (save-excursion 18 | (save-restriction 19 | (narrow-to-region (point-min) end) 20 | (goto-char start) 21 | (while (re-search-forward "^ *)" nil t) 22 | (forward-char -1) 23 | (paredit-close-parenthesis)))) 24 | (move-to-column c))) 25 | (pushnew 'fix-trailing-parens *lisp-format-fixers*) 26 | 27 | ;;; Syntax table extension for curry-compose-reader-macros 28 | (modify-syntax-entry ?\[ "(]" lisp-mode-syntax-table) 29 | (modify-syntax-entry ?\] ")[" lisp-mode-syntax-table) 30 | (modify-syntax-entry ?\{ "(}" lisp-mode-syntax-table) 31 | (modify-syntax-entry ?\} "){" lisp-mode-syntax-table) 32 | (modify-syntax-entry ?\« "(»" lisp-mode-syntax-table) 33 | (modify-syntax-entry ?\» ")«" lisp-mode-syntax-table) 34 | 35 | ;;; Specify indentation levels for specific functions. 36 | (mapc (lambda (pair) (put (first pair) 'lisp-indent-function (second pair))) 37 | '((make-instance 1) 38 | (if-let 1) 39 | (if-let* 1) 40 | (when-let 1) 41 | (when-let* 1) 42 | (defixture 1))) 43 | 44 | ;;; Work around a bug in `lisp-indent-calc-next' in which it can hang 45 | ;;; on a dangling unmatched |# character. The change in the code 46 | ;;; below against the original is the addition of the block and the 47 | ;;; throw to the block when we're in the same position (the newly 48 | ;;; added last-last-sexp matches last-sexp). Since I'm not sure if it 49 | ;;; is possible in normal execution for last-sexp to stay the same I 50 | ;;; only abort if this happens 100 times in a row (pretty cheap). 51 | (defun lisp-indent-calc-next (state) 52 | "Move to next line and return calculated indent for it. 53 | STATE is updated by side effect, the first state should be 54 | created by `lisp-indent-initial-state'. This function may move 55 | by more than one line to cross a string literal." 56 | (pcase-let* (((cl-struct lisp-indent-state 57 | (stack indent-stack) ppss ppss-point) 58 | state) 59 | (indent-depth (car ppss)) ; Corresponding to indent-stack. 60 | (depth indent-depth) 61 | (last-last-sexp nil) (counter 0)) 62 | (block nil 63 | ;; Parse this line so we can learn the state to indent the 64 | ;; next line. 65 | (while (let ((last-sexp (nth 2 ppss))) 66 | (setq ppss (parse-partial-sexp 67 | ppss-point (progn (end-of-line) (point)) 68 | nil nil ppss)) 69 | ;; Preserve last sexp of state (position 2) for 70 | ;; `calculate-lisp-indent', if we're at the same depth. 71 | (if (and (not (nth 2 ppss)) (= depth (car ppss))) 72 | (setf (nth 2 ppss) last-sexp) 73 | (setq last-sexp (nth 2 ppss))) 74 | (setq depth (car ppss)) 75 | (if (and (>= counter 100) (equal last-sexp last-last-sexp)) 76 | (progn (setf indent-stack nil) (return)) 77 | (setq last-last-sexp last-sexp 78 | counter (1+ counter))) 79 | ;; Skip over newlines within strings. 80 | (nth 3 ppss)) 81 | (let ((string-start (nth 8 ppss))) 82 | (setq ppss (parse-partial-sexp (point) (point-max) 83 | nil nil ppss 'syntax-table)) 84 | (setf (nth 2 ppss) string-start) ; Finished a complete string. 85 | (setq depth (car ppss))) 86 | (setq ppss-point (point))) 87 | (setq ppss-point (point)) 88 | (let* ((depth-delta (- depth indent-depth))) 89 | (cond ((< depth-delta 0) 90 | (setq indent-stack (nthcdr (- depth-delta) indent-stack))) 91 | ((> depth-delta 0) 92 | (setq indent-stack (nconc (make-list depth-delta nil) 93 | indent-stack)))))) 94 | 95 | (prog1 96 | (let (indent) 97 | (cond ((= (forward-line 1) 1) nil) 98 | ;; Negative depth, probably some kind of syntax error. 99 | ((null indent-stack) 100 | ;; Reset state. 101 | (setq ppss (parse-partial-sexp (point) (point)))) 102 | ((car indent-stack)) 103 | ((integerp (setq indent (calculate-lisp-indent ppss))) 104 | (setf (car indent-stack) indent)) 105 | ((consp indent) ; (COLUMN CONTAINING-SEXP-START) 106 | (car indent)) 107 | ;; This only happens if we're in a string. 108 | (t (error "This shouldn't happen")))) 109 | (setf (lisp-indent-state-stack state) indent-stack) 110 | (setf (lisp-indent-state-ppss-point state) ppss-point) 111 | (setf (lisp-indent-state-ppss state) ppss)))) 112 | -------------------------------------------------------------------------------- /test/hangs/args: -------------------------------------------------------------------------------- 1 | -style=file 2 | -------------------------------------------------------------------------------- /test/hangs/check: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # If the prior command didn't hang, then we win. 3 | exit 0 4 | -------------------------------------------------------------------------------- /test/hangs/delta-debug-run: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | FILE=$(mktemp test-XXXXXXXX.lisp) 3 | ERROR=0 4 | function exit_hook (){ 5 | rm -f $FILE 6 | exit $ERROR;} 7 | trap exit_hook SIGHUP SIGINT SIGTERM 8 | cat -- > $FILE 9 | # echo $FILE >&2 10 | timeout 10 lisp-format -style=file -lines=0:$(wc -l $FILE) $FILE >/dev/null 2>/dev/null 11 | -------------------------------------------------------------------------------- /test/hangs/example.lisp: -------------------------------------------------------------------------------- 1 | |# 2 | -------------------------------------------------------------------------------- /test/not-add-tabs/.lisp-format: -------------------------------------------------------------------------------- 1 | ;;; With this set tabs should not be used by default. 2 | (set-default 'indent-tabs-mode nil) 3 | -------------------------------------------------------------------------------- /test/not-add-tabs/args: -------------------------------------------------------------------------------- 1 | -style=file -lines=0:7 -------------------------------------------------------------------------------- /test/not-add-tabs/check: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Ensure output has no tabs. 3 | if [ ! -f output.lisp ];then 4 | echo "No output produced!" 5 | exit 1 6 | else 7 | # The 'grep -v' drops the line that had tabs in the original. 8 | if $(grep -v ":object variant" output.lisp|grep -qP "\t");then 9 | echo "Tab remains in output.lisp." 10 | exit 1 11 | else 12 | echo "Success." 13 | exit 0 14 | fi 15 | fi 16 | -------------------------------------------------------------------------------- /test/not-add-tabs/delta-debug-run: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | FILE=$(mktemp test-XXXXXXXX.lisp) 3 | ERROR=2 4 | function exit_hook (){ 5 | rm -f $FILE 6 | exit $ERROR;} 7 | trap exit_hook SIGHUP SIGINT SIGTERM 8 | cat -- > $FILE 9 | # echo $FILE >&2 10 | START=$(grep -n '(deftest asm-replace-operand-maintains-length ()' $FILE|cut -d: -f1) 11 | if [ -z "$START" ];then 12 | START=0 13 | fi 14 | END=$((START + 7)) 15 | echo "-lines=$START:$END" >&2 16 | OUTPUT=$(lisp-format -style=file -lines=$START:$END $FILE 2>/dev/null) 17 | if [ -z "$OUTPUT" ];then 18 | # Don't fail if no output is produced. 19 | ERROR=0 20 | else 21 | echo "$OUTPUT"|grep -qP "\t" 22 | if [ $? -eq 0 ];then 23 | # Only fail if output produced and it contains a TAB. 24 | ERROR=3 25 | else 26 | # Don't fail if output produced and no TAB. 27 | ERROR=0 28 | fi 29 | fi 30 | exit $ERROR 31 | -------------------------------------------------------------------------------- /test/not-add-tabs/delta-debug-run-simple: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | ERROR=2 3 | START=$(grep -n '(deftest asm-replace-operand-maintains-length ()' $1|cut -d: -f1) 4 | if [ -z "$START" ];then 5 | START=0 6 | fi 7 | END=$((START + 7)) 8 | OUTPUT=$(lisp-format -style=file -lines=$START:$END $1 2>/dev/null) 9 | 10 | if [ -z "$OUTPUT" ];then 11 | # Fail if no output is produced. 12 | ERROR=4 13 | else 14 | echo "$OUTPUT"|grep -qP "\t" 15 | if [ $? -eq 0 ];then 16 | # Only pass if output produced and it contains a TAB. 17 | ERROR=0 18 | else 19 | # Don't fail if output produced and no TAB. 20 | ERROR=3 21 | fi 22 | fi 23 | exit $ERROR 24 | -------------------------------------------------------------------------------- /test/not-add-tabs/example.lisp: -------------------------------------------------------------------------------- 1 | (deftest homologous-crossover-with-cut () 2 | (with-fixture gcd-asm 3 | (let ((variant (copy *gcd*)) 4 | (target 40)) 5 | ;; apply cut to variant 6 | (apply-mutation variant 7 | (make-instance 'simple-cut 8 | :object variant 9 | --------------------------------------------------------------------------------