|
1 | 1 | ## Doctor Pretty |
2 | 2 |
|
| 3 | +> A Swift implementation of the [A prettier printer (Wadler 2003)](https://homepages.inf.ed.ac.uk/wadler/papers/prettier/prettier.pdf) ported from [wl-pprint-annotated](https://github.com/minad/wl-pprint-annotated/blob/master/src/Text/PrettyPrint/Annotated/WL.hs) |
| 4 | +
|
3 | 5 | [](https://travis-ci.org/bkase/DoctorPretty) |
4 | 6 |
|
5 | | -A Swift implementation of the [A prettier printer (Wadler 2003)](https://homepages.inf.ed.ac.uk/wadler/papers/prettier/prettier.pdf) paper (including generally accepted modern enhancements ala [wl-pprint-annotated](https://github.com/minad/wl-pprint-annotated/blob/master/src/Text/PrettyPrint/Annotated/WL.hs). |
| 7 | +## What is this |
| 8 | + |
| 9 | +A pretty printer is the dual of a parser -- take some arbitrary AST and output it to a String. This library is a collection of combinators and a primitive called a `Doc` to describe pretty printing some data much like a parser combinator library provides combinators and primitives to describe parsing test into an AST. Interestingly, this implementation efficiently finds the _best_ pretty print. You encode your knowledge of what the _best_ means with your use of various `Doc` combinators. |
| 10 | + |
| 11 | +For example: Let's say we have some internal structured representation of this Swift code: |
| 12 | + |
| 13 | +```swift |
| 14 | +func aLongFunction(foo: String, bar: Int, baz: Long) -> (String, Int, Long) { |
| 15 | + sideEffect() |
| 16 | + return (foo, bar, baz) |
| 17 | +} |
| 18 | +``` |
| 19 | + |
| 20 | +With this library the description that pretty prints the above at a page width of 120 characters. Also prints: |
| 21 | + |
| 22 | +At a page-width of 40 characters: |
| 23 | + |
| 24 | +```swift |
| 25 | +func aLongFunction( |
| 26 | + foo: String, bar: Int, baz: Long |
| 27 | +) -> (String, Int, Long) { |
| 28 | + sideEffect() |
| 29 | + return (foo, bar, baz) |
| 30 | +} |
| 31 | +``` |
| 32 | + |
| 33 | +and at a page-width of 20 characters: |
| 34 | + |
| 35 | +```swift |
| 36 | +func aLongFunction( |
| 37 | + foo: String, |
| 38 | + bar: Int, |
| 39 | + baz: Long |
| 40 | +) -> ( |
| 41 | + String, |
| 42 | + Int, |
| 43 | + Long |
| 44 | +) { |
| 45 | + sideEffect() |
| 46 | + return ( |
| 47 | + foo, |
| 48 | + bar, |
| 49 | + baz |
| 50 | + ) |
| 51 | +} |
| 52 | +``` |
| 53 | + |
| 54 | +See the encoding of this particular document in the [`testSwiftExample` test case](Tests/DoctorPrettyTests/DoctorPrettyTests.swift). |
| 55 | + |
| 56 | +## What would I use this for? |
| 57 | + |
| 58 | +If you're outputting text and you care about the width of the page. Serializing to a `Doc` lets you capture your line-break logic and how your output string looks in one go. |
| 59 | + |
| 60 | +Why would you output text and care about page width? |
| 61 | + |
| 62 | +1. You're building a `gofmt`-type tool for Swift (Note: `gofmt` doesn't pretty-print based on a width, `refmt` (Reason) and `prettier` (JavaScript) do) |
| 63 | +2. You're writing some sort of codegen tool to output Swift code |
| 64 | +3. You're building a source-to-source transpiler |
| 65 | +2. You're outputing help messages in a terminal window for some commandline app (I'm planning to use this for https://github.com/bkase/swift-optparse-applicative) |
| 66 | + |
| 67 | +## What is this, actually |
| 68 | + |
| 69 | +A Swift implementation of the [A prettier printer (Wadler 2003)](https://homepages.inf.ed.ac.uk/wadler/papers/prettier/prettier.pdf) paper (including generally accepted modern enhancements ala [wl-pprint-annotated](https://github.com/minad/wl-pprint-annotated/blob/master/src/Text/PrettyPrint/Annotated/WL.hs). This implementation is close to a direct port of [wl-pprint-annotated](https://github.com/minad/wl-pprint-annotated/blob/master/src/Text/PrettyPrint/Annotated/WL.hs) with some influence from [scala-optparse-applicative's Doc](https://github.com/bmjames/scala-optparse-applicative/blob/master/src/main/scala/net/bmjames/opts/types/Doc.scala) and a few extra Swiftisms. |
| 70 | + |
| 71 | +## Basic Usage |
| 72 | + |
| 73 | +`Doc` is very composable. First of all it's a [`monoid`](https://www.youtube.com/watch?v=6z9QjDUKkCs) with an `.empty` document and the `.concat` case which just puts two documents next to each other. We also have a primitive called `grouped`, which tries this document on a single line, but if it doesn't fit then breaks it up on new-lines. From there we build all high-level combinators up. |
| 74 | + |
| 75 | +`x <%> y` concats x and y with a space in between if it fits, otherwise puts a line. |
| 76 | + |
| 77 | +```swift |
| 78 | +.text("foo") <%> .text("bar") |
| 79 | +``` |
| 80 | + |
| 81 | +pretty-prints under a large page-width: |
| 82 | + |
| 83 | +``` |
| 84 | +foo bar |
| 85 | +``` |
| 86 | + |
| 87 | +but when the page-width is set to `5` prints: |
| 88 | + |
| 89 | +``` |
| 90 | +foo |
| 91 | +bar |
| 92 | +``` |
| 93 | + |
| 94 | +Here are a few more combinators: |
| 95 | + |
| 96 | +```swift |
| 97 | +/// Concats x and y with a space in between |
| 98 | +static func <+>(x: Doc, y: Doc) -> Doc |
| 99 | + |
| 100 | +/// Behaves like `space` if the output fits the page |
| 101 | +/// Otherwise it behaves like line |
| 102 | +static var softline |
| 103 | + |
| 104 | +/// Concats x and y together if it fits |
| 105 | +/// Otherwise puts a line in between |
| 106 | +static func <%%>(x: Doc, y: Doc) -> Doc |
| 107 | + |
| 108 | +/// Behaves like `zero` if the output fits the page |
| 109 | +/// Otherwise it behaves like line |
| 110 | +static var softbreak: Doc |
| 111 | + |
| 112 | +/// Puts a line between x and y that can be flattened to a space |
| 113 | +static func <&>(x: Doc, y: Doc) -> Doc |
| 114 | + |
| 115 | +/// Puts a line between x and y that can be flattened with no space |
| 116 | +static func <&&>(x: Doc, y: Doc) -> Doc |
| 117 | +``` |
| 118 | + |
| 119 | +There are also combinators for turning collections of documents into "collection"-like pretty-printed primitives such as a square-bracket separated lists: |
| 120 | + |
| 121 | +```swift |
| 122 | +.text("let x =") <%> [ "foo", "bar", "baz" ].map(Doc.text).list(indent: 4) |
| 123 | +``` |
| 124 | + |
| 125 | +Pretty-prints at page-width 80 to: |
| 126 | + |
| 127 | +```swift |
| 128 | +let x = [foo, bar, baz] |
| 129 | +``` |
| 130 | + |
| 131 | +and at page-width 10 to: |
| 132 | + |
| 133 | +```swift |
| 134 | +let x = [ |
| 135 | + foo, |
| 136 | + bar, |
| 137 | + baz |
| 138 | +] |
| 139 | +``` |
| 140 | + |
| 141 | +See the source for more documentation, I have included descriptive doc-comments to explain all the operators (mostly taken from [wl-pprint-annotated](https://github.com/minad/wl-pprint-annotated/blob/master/src/Text/PrettyPrint/Annotated/WL.hs)). |
| 142 | + |
| 143 | +## How do I actually pretty print my documents? |
| 144 | + |
| 145 | +`Doc` has two rendering methods for now: `renderPrettyDefault` prints with a page-width of 100 and `renderPretty` lets you control the page-width. |
| 146 | + |
| 147 | +These methods don't return `String`s directly -- they return `SimpleDoc` a low-level IR that is close to a string, but high-enough that you can efficiently output to some other output system like stdout or a file. |
| 148 | + |
| 149 | +For now, `SimpleDoc` has `displayString()` which outputs a `String`, and: |
| 150 | + |
| 151 | +```swift |
| 152 | +func display<M: Monoid>(readString: (String) -> M) -> M |
| 153 | +``` |
| 154 | + |
| 155 | +`display` takes a function that can turn a string into a monoid and then smashes everything together. Because this works for any monoid, you just need to provide a monoid instance for your output formatter (to write to stdout or to a file). |
| 156 | + |
| 157 | +## Installation |
| 158 | + |
| 159 | +With Swift Package Manager, put this inside your `Package.swift`: |
6 | 160 |
|
7 | | ---- |
| 161 | +```swift |
| 162 | +.Package(url: "https://github.com/bkase/DoctorPretty.git", |
| 163 | + majorVersion: 0, minor: 2) |
| 164 | +``` |
8 | 165 |
|
9 | | -## WIP do not use yet |
| 166 | +## How does it work? |
10 | 167 |
|
11 | | -The implementation does not have larger stress tests yet. |
| 168 | +Read the [A prettier printer (Wadler 2003) paper](https://homepages.inf.ed.ac.uk/wadler/papers/prettier/prettier.pdf). |
12 | 169 |
|
13 | | -Please feel free to contribute though! |
| 170 | +`Doc` is a recursive enum that captures text and new lines. The interesting case is `.union(longerLines: Doc, shorterLines: Doc)`. This case reifies the notion of "try the longer lines first, then the shorter lines". We can build all sorts of high-level combinators that combine `Doc`s in different ways that eventually reduce to a few strategic `.union`s. |
14 | 171 |
|
15 | | -In addition to the paper, I'm using [wl-pprint-annotated](https://github.com/minad/wl-pprint-annotated/blob/master/src/Text/PrettyPrint/Annotated/WL.hs) as a guide for this implementation. |
| 172 | +The renderer keeps a work-list and each rule removes or adds pieces of work to the list and recurses until the list is empty. The best-fit metric proceeds greedily for now, but can be swapped out easily for a more intelligent algorithm in the future. |
16 | 173 |
|
0 commit comments