(rontolisp) docs

Formatting Source Code

rontolisp format re-indents Lisp source files in place. Point it at a file or at a directory and every .lisp and .asd file under it is rewritten to one canonical layout, so indentation stops being something anyone has to think about or review.

rontolisp format app.lisp          # one file
rontolisp format src/              # every .lisp / .asd under src/
rontolisp format src/ tests/       # several paths

Only whitespace changes. Every token is reproduced exactly as written -- including its case, so Foo stays Foo -- and strings, character literals, block comments and #+/#- guards are copied through untouched. The formatted file reads as precisely the same program; nothing is macroexpanded, evaluated, or loaded, so a file formats whether or not its dependencies are installed.

Files that are already formatted are left alone, not even rewritten, so the command is safe to run over a whole tree repeatedly.

Options

OptionMeaning
--checkDo not write anything. List the files that are not formatted and exit 1 if there are any.
--stdoutWrite the result to standard output instead of the file (one file only).
--width=NRight margin to wrap to. Default 80.
-h, --helpShow the command's help.

A - in place of a path formats standard input to standard output, which is what an editor's "format buffer" command wants:

echo '(let ((a 1)(b 2))(+ a b))' | rontolisp format -
(let ((a 1) (b 2)) (+ a b))

--check writes nothing and fails when the tree is not formatted, which makes it a one-line CI gate:

rontolisp format --check src/ || { echo "run: rontolisp format src/"; exit 1; }

What the layout looks like

A form that fits within the margin goes on one line. One that does not breaks according to what its operator is.

A definition keeps its name and lambda list on the first line and indents the body by two:

cond clauses line up under the first clause. A clause too wide for one line puts its body below its test -- unless the test is a single token like t and the body is one form, in which case the body stays beside it rather than leaving a line to that token, so long as it needs no extra line and no extra width there.

if puts its two branches under the test, so they read as a pair rather than as a body:

let bindings line up under the first binding, once they stop fitting on the let's own line:

A function call puts its arguments under the first one, and keeps each :keyword value option together on a line of its own:

(with-open-file (out "report.txt"
                     :direction :output
                     :if-exists :supersede
                     :if-does-not-exist :create)
  (write-line "done" out))

loop gets one line per clause, aligned under the first:

Two body forms are always two lines

A body of two or more forms is a sequence performed in order, so it gets a line each however short it is -- the same reason no formatter of a C-like language will put two statements on one line. A body of exactly one form may share the header's line:

but a body of two may not, even though it would fit:

This is also what keeps the output stable: a two-form body will not silently join because a rename made it two characters shorter.

Comments

A comment that started its own line keeps one, at the indentation of the code around it. A comment that trailed code stays on that code's line, and the trailing comments of consecutive lines are lined up into a column:

(setq width 80)      ; the right margin
(setq body-indent 2) ; body indentation
(setq tabs nil)      ; spaces only

Blank lines

A blank line is the only paragraph break Lisp source has, so wherever you left one it stays -- inside a body as well as between top-level forms. A run of several collapses to one, and no blank line is ever added.

Limits

Line comments and string literals are never re-wrapped: their content is yours, not the formatter's. A line whose content simply cannot be broken -- a long string, a deeply nested expression with no shorter arrangement -- may therefore end up past the margin.

A macro the formatter has not been told about is laid out from its name: with-... and do-... take one argument then a body, def... takes a name and then a body, and anything else is laid out as a function call.

A def... macro keeps a lambda list on its first line the way defun does, but only when its second element could BE a lambda list -- a list of plain parameter names, possibly empty or with &optional/&rest/&key markers. A keyword, a string, a number, nil, t or a nested form in that position means the element is the first form of the body instead, and it gets a line of its own:

(define-get "/hello" () (ok "hello world"))

(define-routes *app*
  (define-get "/hello" () (ok "hello world"))
  (define-any "*" () (not-found "nope")))

If your own macro takes a different shape, write it in the layout you want and the formatter will keep it as long as it fits on one line.