bobbicodes / bobbi-lisp

Interactive Lisp environment for learning Clojure
https://bobbicodes.github.io/bobbi-lisp/
0 stars 0 forks source link

[editor] formatting/pretty printing #21

Closed bobbicodes closed 11 months ago

bobbicodes commented 11 months ago

There is a pretty printer that ships with Mal already, I wonder if I could put that in between the evaluation output and the text that gets dispatched to the editor.

;; Pretty printer a MAL object.

(def! pprint

  (let* [

    spaces- (fn* [indent]
      (if (> indent 0)
        (str " " (spaces- (- indent 1)))
        ""))

    pp-seq- (fn* [obj indent]
      (let* [xindent (+ 1 indent)]
        (apply str (pp- (first obj) 0)
                   (map (fn* [x] (str "\n" (spaces- xindent)
                                      (pp- x xindent)))
                        (rest obj)))))

    pp-map- (fn* [obj indent]
      (let* [ks (keys obj)
             kindent (+ 1 indent)
             kwidth (count (seq (str (first ks))))
             vindent (+ 1 (+ kwidth kindent))]
        (apply str (pp- (first ks) 0)
                   " "
                   (pp- (get obj (first ks)) 0)
                   (map (fn* [k] (str "\n" (spaces- kindent)
                                      (pp- k kindent)
                                      " "
                                      (pp- (get obj k) vindent)))
                        (rest (keys obj))))))

    pp- (fn* [obj indent]
      (cond
        (list? obj)   (str "(" (pp-seq- obj indent) ")")
        (vector? obj) (str "[" (pp-seq- obj indent) "]")
        (map? obj)    (str "{" (pp-map- obj indent) "}")
        :else         (pr-str obj)))

    ]

    (fn* [obj]
         (println (pp- obj 0)))))
bobbicodes commented 11 months ago

Besides wiring it to the output, it would be nice to have a key, like tab, format the contents of the editor (or even as you type)

bobbicodes commented 11 months ago

I tested this and it's actually kind of weird. It puts a newline after every... everything. Would it maybe make sense to use something like clj-fmt? I looked at zprint, which I think is more designed for my use case, but it only runs in linux. I wonder if that's why Calva uses clj-fmt (at least I think it does).

Wait... what about fipp (Brandon Bloom's fast idiomatic pretty printer)? Planck uses that, and it might be perfect.

bobbicodes commented 11 months ago

Actually, puget is based on fipp and also colorizes the output, and is designed specifically for generating a canonical representation. It actually has an option to add HTML span elements with inline style attributes!

The biggest question I have is how this will play with Codemirror. Surely they must have thought of this...

Also, I met Greg Look a couple of times at the Seattle Clojure meetup and he's a really nice guy! So is Brandon. Not that I would have assumed otherwise.

bobbicodes commented 11 months ago

I checked these out and unfortunately, they use way too much of the Clojure language to run with my interpreter for the time being.

There's a possibility that I could run a Clojure process to accomplish it though... but it seems more in the spirit of the project to have my own pretty printer. I could start with the basic one included with Mal and build off of it.

What does borkdude use for pretty printing in jet? Hmm, from a quick glance it seems like it doesn't.

bobbicodes commented 11 months ago

The clojure.pprint documentation has some good info to learn about the concepts: https://clojure.github.io/clojure/branch-master/doc/clojure/pprint/PrettyPrinting.html

Basic Concepts of Pretty Printing

In order to create custom dispatch functions, you need to understand the fundamentals of pretty printing. The clojure pretty printer is based on the XP pretty printer algorithm (used in many Lisps including Common Lisp) which supports sophisticated decision-making about line breaking and indentation with reasonable performance even for very large structures. The XP algorithm is documented in the paper, XP. A Common Lisp Pretty Printing System.

The Clojure implementation of XP is similar in spirit to the Common Lisp implementation, but the details of the interface are somewhat different. The result is that writing custom dispatch in Clojure is more "Clojure-y."

There are three key concepts to understand when creating custom pretty printing functions: logical blocks, conditional newlines, and indentation.

A logical block marks a set of output that should be thought about as a single unit by the pretty printer. Logical blocks can contain other logical blocks (that is, they nest). As a simple example, when printing list structure, every sublist will typically be a logical block.

Conditional newlines tell the pretty printer where it can insert line breaks and how to make the decisions about when to do it. There are four types of conditional newline:

Linear newlines tell the pretty printer to insert a newline in a place whenever the enclosing logical block won't fit on a single line. Linear newlines are an all-or-nothing proposition; if the logical block doesn't fit on a single line, all the linear newlines are emitted as actual newlines.

Fill newlines tell the pretty printer that it should fit as many chunks of the logical block as possible on this line and then emit a newline.

Mandatory newlines tell the pretty printer to emit a newline regardless of where it is in the output line.

Miser newlines tell the pretty printer to emit a newline if the output column is in the miser region (as defined by the pretty printer variable pprint-miser-width). This allows you to define special behavior as the output gets heavily nested near the right margin.

Indentation commands allow you to specify how wrapped lines should be indented. Indentation can be relative to either the start column of the current logical block or the current column position of the output.

bobbicodes commented 11 months ago

I'm going to close this and call the pretty printing "good enough". It turns out I really like it!