• Stars
    star
    192
  • Rank 202,019 (Top 4 %)
  • Language
    Swift
  • License
    Apache License 2.0
  • Created over 7 years ago
  • Updated about 3 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Wadler's "A prettier printer" embedded pretty-printer DSL for Swift

Doctor Pretty

A Swift implementation of the A prettier printer (Wadler 2003) ported from wl-pprint-annotated

Build Status

What is this

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.

For example: Let's say we have some internal structured representation of this Swift code:

func aLongFunction(foo: String, bar: Int, baz: Long) -> (String, Int, Long) {
    sideEffect()
    return (foo, bar, baz)
}

With this library the description that pretty prints the above at a page width of 120 characters. Also prints:

At a page-width of 40 characters:

func aLongFunction(
    foo: String, bar: Int, baz: Long
) -> (String, Int, Long) {
    sideEffect()
    return (foo, bar, baz)
}

and at a page-width of 20 characters:

func aLongFunction(
    foo: String,
    bar: Int,
    baz: Long
) -> (
    String,
    Int,
    Long
) {
    sideEffect()
    return (
        foo,
        bar,
        baz
    )
}

See the encoding of this particular document in the testSwiftExample test case.

What would I use this for?

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.

Why would you output text and care about page width?

  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)
  2. You're writing some sort of codegen tool to output Swift code
  3. You're building a source-to-source transpiler
  4. 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)

What is this, actually

A Swift implementation of the A prettier printer (Wadler 2003) paper (including generally accepted modern enhancements ala wl-pprint-annotated. This implementation is close to a direct port of wl-pprint-annotated with some influence from scala-optparse-applicative's Doc and a few extra Swiftisms.

Basic Usage

Doc is very composable. First of all it's a monoid 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.

x <%> y concats x and y with a space in between if it fits, otherwise puts a line.

.text("foo") <%> .text("bar")

pretty-prints under a large page-width:

foo bar

but when the page-width is set to 5 prints:

foo
bar

Here are a few more combinators:

/// Concats x and y with a space in between
static func <+>(x: Doc, y: Doc) -> Doc

/// Behaves like `space` if the output fits the page
/// Otherwise it behaves like line
static var softline

/// Concats x and y together if it fits
/// Otherwise puts a line in between
static func <%%>(x: Doc, y: Doc) -> Doc

/// Behaves like `zero` if the output fits the page
/// Otherwise it behaves like line
static var softbreak: Doc

/// Puts a line between x and y that can be flattened to a space
static func <&>(x: Doc, y: Doc) -> Doc

/// Puts a line between x and y that can be flattened with no space
static func <&&>(x: Doc, y: Doc) -> Doc

There are also combinators for turning collections of documents into "collection"-like pretty-printed primitives such as a square-bracket separated lists:

.text("let x =") <%> [ "foo", "bar", "baz" ].map(Doc.text).list(indent: 4)

Pretty-prints at page-width 80 to:

let x = [foo, bar, baz]

and at page-width 10 to:

let x = [
    foo,
    bar,
    baz
]

See the source for more documentation, I have included descriptive doc-comments to explain all the operators (mostly taken from wl-pprint-annotated).

How do I actually pretty print my documents?

Doc has two rendering methods for now: renderPrettyDefault prints with a page-width of 100 and renderPretty lets you control the page-width.

These methods don't return Strings 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.

For now, SimpleDoc has displayString() which outputs a String, and:

func display<M: Monoid>(readString: (String) -> M) -> M

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

Installation

With Swift Package Manager, put this inside your Package.swift:

.Package(url: "https://github.com/bkase/DoctorPretty.git",
         majorVersion: 0, minor: 5)

How does it work?

Read the A prettier printer (Wadler 2003) paper.

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 Docs in different ways that eventually reduce to a few strategic .unions.

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.

More Repositories

1

CUDA-grep

grep on CUDA
C++
116
star
2

cyklic

A Cycle.js inspired Native Single-Atom-State Purely Functional Reactive Composable UI Component library for Android
Kotlin
89
star
3

barbq

πŸ–barbq is a text based status bar for macOS
Haskell
44
star
4

gameboy

[alpha] Rust WASM gameboy emulator aiming for high framerate on mobile browsers
Assembly
31
star
5

swift-di-explorations

Functional DI explorations in Swift
Swift
28
star
6

life

A Nix configuration for macOS and Linux
Nix
27
star
7

git-paradox

Copy history forward safely and easily
Shell
26
star
8

slides

Slide decks rendered from mardown
JavaScript
24
star
9

swift-optparse-applicative

Commandline parsing using applicatives (optparse-applicative ported from Haskell/Scala)
Swift
10
star
10

PathySwift

Typesafe Phantom-Type backed Paths (port of purescript-pathy)
Swift
9
star
11

reasonable-wm

A functional tiling "window manager" for the web written in Reason
HTML
9
star
12

empydom

Python-Javascript DOM bridge
JavaScript
8
star
13

kson

It's like GSON, but in Swift
Swift
7
star
14

abstraction

Data abstraction in Dhall
Dhall
6
star
15

xbox-one-fake-driver

A "joystick driver" for Xbox One controllers on OSX
Go
6
star
16

rust-torrent

Rust torrent client (to play with rust)
Rust
6
star
17

purescript-cache

Algebraic caches for purescript
PureScript
6
star
18

android-scala-example

Example Scala Android project with guide
Scala
4
star
19

Twitter-fake

Twitter fake for reason
OCaml
4
star
20

dotfiles

dotfiles and other configuration files that reside in my home directory
Vim Script
4
star
21

joyfull

Use a joystick in the browser
C
3
star
22

toy-cool-language

A type checker and interpreter for Sound and Complete Bidirectional Typechecking for Higher-Rank Polymorphism with Existentials and Indexed Types (Joshua Dunfield, Neelakantan R. Krishnaswami)
OCaml
3
star
23

osx-life

A Nix-shell default.nix for my life in OSX
Nix
2
star
24

Algorithm-w-again

Doing Algorithm W again [Damas, Milner 82] and [Milner 77-78]
OCaml
2
star
25

key-injector

Inject keyboard events into OSX
Python
2
star
26

compiler

Playing with compilers (WASM?)
Haskell
2
star
27

dotvim

My vim configuration
Vim Script
2
star
28

flickrbox

JavaScript
2
star
29

static-electric

A typesafe static site DSL using JSX/Tyxml and a styling group
OCaml
2
star
30

js-async-to-sync

Experiments in asyncToSync converters in web workers
JavaScript
2
star
31

jsonschema-to-dhall

Auto-generate Dhall bindings given a JSON Schema definition
Nix
2
star
32

drag-drop-in-cyclejs

Playing around with cycle.js; simple drag-drop-uploader
JavaScript
1
star
33

NextBart

Find the when the next bart is leaving
Java
1
star
34

codeshare

1
star
35

site2

Trying to make a website again
Reason
1
star
36

haskell-play

Nix playground for haskell stuff
Haskell
1
star
37

buckitup

Playing with facebook/buck#810 in an Android app
Java
1
star
38

rust-ffi-example

An example of Rust's FFI for v0.4
JavaScript
1
star
39

Native-OpenCV-Android-4.0.3

OpenCV Tutorial 2 Advanced working in Android 4.0.3
Java
1
star
40

datalog-gadt

Type-safe Datalog experiment using GADTs in OCaml
OCaml
1
star
41

fast-blog

Static blog generator
CSS
1
star
42

looking-glass

Through the looking glass, to the world
Java
1
star
43

swift-typed-ast-parser

Swift Typed AST Parser
Swift
1
star
44

cairo-schnorr

Schnorr in cairo (allegedly)
Cairo
1
star
45

algebra-dummit-foote

Exercises for "Abstract Algebra by Dummit and Foote"
TeX
1
star