Skip to content

Commit b7230da

Browse files
authored
Thorough README (#4)
1 parent bf8d709 commit b7230da

File tree

2 files changed

+164
-7
lines changed

2 files changed

+164
-7
lines changed

README.md

Lines changed: 163 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,173 @@
11
## Doctor Pretty
22

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+
35
[![Build Status](https://travis-ci.org/bkase/DoctorPretty.svg?branch=master)](https://travis-ci.org/bkase/DoctorPretty)
46

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`:
6160

7-
---
161+
```swift
162+
.Package(url: "https://github.com/bkase/DoctorPretty.git",
163+
majorVersion: 0, minor: 2)
164+
```
8165

9-
## WIP do not use yet
166+
## How does it work?
10167

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).
12169

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.
14171

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.
16173

Sources/Doc.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ indirect enum Doc {
2121
/// Renders Doc with an increased indent level
2222
/// Note: This only affects line after the first newline
2323
case nest(IndentLevel, Doc)
24-
/// Invariant: longerLines.count >= shorterLines.split('\n').first.count
24+
/// Invariant: longerLines.firstLine.count >= shorterLines.firstLine.count
2525
case union(longerLines: Doc, shorterLines: Doc)
2626
// No support for Annotations for now, I don't think Swift's generics would take kindly to a Doc<A>
2727
// case annotate(A, Doc)

0 commit comments

Comments
 (0)