Code formatter

104 views
Skip to first unread message

John Harrop

unread,
Nov 5, 2009, 1:48:45 PM11/5/09
to clo...@googlegroups.com
The following, which I relinquish into the public domain and certify is original to me, takes a string and reformats it as Clojure code, returning a string. It behaves similarly to the Enclojure reformatter, but:
1. Outside string literals and comments, it will take care of all
   spacing.
2. Comments get one space before and one after the ; before the
   comment text. If the comment text originally started with a space
   none is inserted after the ;.
3. If two comments are on successive lines and the second can be
   aligned with the first by inserting additional spaces before the
   ;, the comments are aligned in this manner; exception to item 2.
4. Delimiters in character literals (e.g. \{) don't screw up
   the indenting of susbequent lines.

Note that this doesn't use the reader, which at first blush would seem to make things simpler. But using the reader would clobber comments, expand #() and the like, and fail if the input contained unbalanced delimiters. The below code appears to indent the same as Enclojure in the face of unbalanced delimiters.

As a little demonstration, the code below appears as formatted by itself:

(defn format-code [string]
  (loop [s string col 0 dstack [] out [] space nil incl false
         insl false incm false lwcr false sups true cmindent nil]
    (if-let [c (first s)] ; test comment 1 { ; )
      (let [r (rest s)    ; test comment 2 ( [
            begins "([{"
            ends ")]}" ; test comment 3 " ;
            delim-indents (zipmap begins [1 0 0]) ; test comment 4
            delim-ends (zipmap ends begins)
            sups-char? #{\' \` \~ \@ \#} ; characters to suppress
            indent (fn []                ; spaces after
                     (if (or (empty? r) (empty? dstack))
                       [\newline]
                       (into [\newline]
                         (repeat
                           (let [[delim pos] (peek dstack)]
                             (+ pos (delim-indents delim)))
                           \space))))
            conc (fn [c]
                   (if sups [c] (conj space c)))
            pop-d (fn [stack c]
                    (if (empty? stack)
                      []
                      (let [ps (pop stack)]
                        (if (empty? ps)
                          []
                          (if (= (first (peek stack)) (delim-ends c))
                            ps
                            (recur ps c))))))]
        (cond
          incm (let [nl (or (= c \newline) (= c \return))
                     spc (Character/isWhitespace c)
                     cc (if nl
                          (indent)
                          (if spc
                            (if-not sups [c])
                            [c]))]
                 (recur
                   r (if nl (dec (count cc)) (+ col (count cc)))
                   dstack (into out cc) nil false false
                   (if-not nl incm) (= c \return) nl cmindent))
          (= c \") (let [cc (if (or incl insl) [c] [\space c])]
                     (recur
                       r (+ col (count cc)) dstack (into out cc)
                       (if (and insl (not incl)) [\space]) false
                       (if incl insl (not insl)) false false false
                       cmindent))
          (or incl insl) (recur
                           r (if (or (= c \newline) (= c \return))
                               0
                               (inc col))
                           dstack (conj out c) nil
                           (and insl (not incl) (= c \\)) insl false
                           false false cmindent)
          (= c \;) (let [padding (if sups 0 1)
                         padding (if cmindent
                                   (max padding (- cmindent col))
                                   padding)
                         padding (repeat padding \space)
                         padding (concat padding [\; \space])]
                     (recur
                       r (+ col (count padding)) dstack
                       (into out padding) nil false false true false
                       true (+ col (count padding) -2)))
          (= c \\) (let [cc (if sups [c] [\space c])]
                     (recur
                       r (+ col (count cc)) dstack (into out cc) nil
                       true false false false false cmindent))
          (or
            (and (= c \newline) (not lwcr))
            (= c \return)) (let [i (indent)]
                             (recur
                               r (dec (count i)) dstack (into out i)
                               nil false false false (= c \return)
                               true nil))
          (= c \newline) (recur
                           r col dstack out nil false false false
                           false true cmindent)
          (Character/isWhitespace c) (recur
                                       r col dstack out [\space]
                                       false false false false sups
                                       cmindent)
          (delim-indents c) (let [cc (if sups [c] [\space c])
                                  cn (count cc)]
                              (recur
                                r (+ col cn)
                                (conj dstack [c (+ col cn)])
                                (into out cc) nil false false false
                                false true cmindent))
          (delim-ends c) (recur
                           r (inc col) (pop-d dstack c) (conj out c)
                           [\space] false false false false false
                           cmindent)
          :else (let [cc (conc c)]
                  (recur
                    r (+ col (count cc)) dstack (into out cc) nil
                    false false false false (sups-char? c)
                    cmindent))))
      (apply str out))))

jan

unread,
Nov 5, 2009, 4:57:43 PM11/5/09
to clo...@googlegroups.com
John Harrop <jharr...@gmail.com> writes:

> The following, which I relinquish into the public domain and certify is
> original to me, takes a string and reformats it as Clojure code, returning a
> string. It behaves similarly to the Enclojure reformatter, but:
> 1. Outside string literals and comments, it will take care of all
>    spacing.
> 2. Comments get one space before and one after the ; before the
>    comment text. If the comment text originally started with a space
>    none is inserted after the ;.
> 3. If two comments are on successive lines and the second can be
>    aligned with the first by inserting additional spaces before the
>    ;, the comments are aligned in this manner; exception to item 2.

it doesn't handle these comments gracefully

(format-code
";;; header comment
(defn test-comments []
;; whole line comment
:thing)")

--
jan

John Harrop

unread,
Nov 5, 2009, 7:46:26 PM11/5/09
to clo...@googlegroups.com
Meh. I haven't tended to use multiple ; in a row. I can make it group them together but it'll be a little more work.

tmountain

unread,
Nov 6, 2009, 9:26:06 AM11/6/09
to Clojure
Very cool. I've been wanting something like this for a while. I got a
laugh because I ran the formatter on itself, and it produced identical
output. This means one of two things:

a) you modeled it after your personal coding style where you adhere to
religiously
b) you had the same idea and filtered it through itself before posting

Anyway, thanks for sharing.

Michael Wood

unread,
Nov 6, 2009, 9:42:31 AM11/6/09
to clo...@googlegroups.com
2009/11/6 tmountain <tinymo...@gmail.com>:

>
> Very cool. I've been wanting something like this for a while. I got a
> laugh because I ran the formatter on itself, and it produced identical
> output. This means one of two things:
>
> a) you modeled it after your personal coding style where you adhere to
> religiously
> b) you had the same idea and filtered it through itself before posting

You appear to have missed this in the announcement e-mail:

> On Nov 5, 1:48 pm, John Harrop <jharrop...@gmail.com> wrote:

[...]


>> As a little demonstration, the code below appears as formatted by itself:

[...]

But I suspect (a) applies to a large extent too.

:)

--
Michael Wood <esio...@gmail.com>

John Harrop

unread,
Nov 6, 2009, 9:45:45 AM11/6/09
to clo...@googlegroups.com
On Fri, Nov 6, 2009 at 9:26 AM, tmountain <tinymo...@gmail.com> wrote:

Very cool. I've been wanting something like this for a while. I got a
laugh because I ran the formatter on itself, and it produced identical
output. This means one of two things:

a) you modeled it after your personal coding style where you adhere to
religiously
b) you had the same idea and filtered it through itself before posting

I thought I'd even *said* I did... 

Tim Dysinger

unread,
Nov 6, 2009, 7:41:34 PM11/6/09
to clo...@googlegroups.com
Use emacs! It formats your code while you type :)

John Harrop

unread,
Nov 6, 2009, 8:35:58 PM11/6/09
to clo...@googlegroups.com
On Fri, Nov 6, 2009 at 7:41 PM, Tim Dysinger <t...@dysinger.net> wrote:
Use emacs! It formats your code while you type :)

Nasty side effect though -- it formats your brain while you type, too. Eventually you wind up a gibbering lunatic. :) I'll keep my Netbeans, thanks. :) 

mudphone

unread,
Nov 7, 2009, 5:22:52 AM11/7/09
to Clojure
Um, I use butterflies?
Reply all
Reply to author
Forward
0 new messages