Skip to content

Commit 4a31b5f

Browse files
committed
Initial commit
0 parents  commit 4a31b5f

17 files changed

+619
-0
lines changed

.gitignore

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
.DS_Store
2+
/.build
3+
/Packages
4+
/*.xcodeproj

Package.pins

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{
2+
"autoPin": true,
3+
"pins": [
4+
{
5+
"package": "Algebra",
6+
"reason": null,
7+
"repositoryURL": "https://github.com/bkase/Algebra.git",
8+
"version": "0.2.0"
9+
},
10+
{
11+
"package": "Operadics",
12+
"reason": null,
13+
"repositoryURL": "https://github.com/typelift/Operadics.git",
14+
"version": "0.2.3"
15+
},
16+
{
17+
"package": "Swiftx",
18+
"reason": null,
19+
"repositoryURL": "https://github.com/bkase/Swiftx.git",
20+
"version": "0.5.3"
21+
}
22+
],
23+
"version": 1
24+
}

Package.swift

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// swift-tools-version:3.1
2+
3+
import PackageDescription
4+
5+
let package = Package(
6+
name: "DoctorPretty",
7+
targets: [],
8+
dependencies: [
9+
.Package(url: "https://github.com/bkase/Algebra.git", majorVersion: 0, minor: 2),
10+
.Package(url: "https://github.com/bkase/Swiftx.git", majorVersion: 0, minor: 5)
11+
]
12+
)

README.md

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
## Doctor Pretty
2+
3+
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).
4+
5+
---
6+
7+
## WIP do not use yet
8+
9+
The implementation is currently broken and does not have tests yet.
10+
11+
Please feel free to contribute though!
12+
13+
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.
14+

Sources/Alignment.swift

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
//
2+
// Alignment.swift
3+
// DoctorPretty
4+
//
5+
// Created by Brandon Kase on 5/20/17.
6+
//
7+
//
8+
9+
import Foundation
10+
import Operadics
11+
12+
// Alignment and Indentation
13+
extension Doc {
14+
/// Indent all lines of the doc by `i`
15+
func indent(_ i: IndentLevel) -> Doc {
16+
return (.text(spaces(i)) <> self).hang(i)
17+
}
18+
19+
/// Hanging indentation
20+
func hang(_ i: IndentLevel) -> Doc {
21+
return (Doc.nest(i, self)).align()
22+
}
23+
24+
/// Align this document with the nesting level set to the current column
25+
func align() -> Doc {
26+
return .column { k in
27+
.nesting { i in .nest(k - i, self) }
28+
}
29+
}
30+
}
31+

Sources/Atoms.swift

+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
//
2+
// Atoms.swift
3+
// DoctorPretty
4+
//
5+
// Created by Brandon Kase on 5/20/17.
6+
//
7+
//
8+
9+
import Foundation
10+
import Operadics
11+
12+
extension Doc {
13+
/// Enclose a doc between left and right
14+
func enclose(left: Doc, right: Doc) -> Doc {
15+
return left <> self <> right
16+
}
17+
18+
var squotes: Doc {
19+
return enclose(left: .squote, right: .squote)
20+
}
21+
22+
var dquotes: Doc {
23+
return enclose(left: .dquote, right: .dquote)
24+
}
25+
26+
var braces: Doc {
27+
return enclose(left: .lbrace, right: .rbrace)
28+
}
29+
30+
var parens: Doc {
31+
return enclose(left: .lparen, right: .rparen)
32+
}
33+
34+
var angles: Doc {
35+
return enclose(left: .langle, right: .rangle)
36+
}
37+
38+
var brackets: Doc {
39+
return enclose(left: .lbracket, right: .rbracket)
40+
}
41+
42+
static let squote: Doc = .char("'")
43+
static let dquote: Doc = .char("\"")
44+
static let lbrace: Doc = .char("{")
45+
static let rbrace: Doc = .char("}")
46+
static let lparen: Doc = .char("(")
47+
static let rparen: Doc = .char(")")
48+
static let langle: Doc = .char("<")
49+
static let rangle: Doc = .char(">")
50+
static let lbracket: Doc = .char("[")
51+
static let rbracket: Doc = .char("]")
52+
53+
static let space: Doc = .char(" ")
54+
static let dot: Doc = .char(".")
55+
static let backslash: Doc = .char("\\")
56+
static let equals: Doc = .char("=")
57+
}

Sources/Doc.swift

+77
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import Swiftx
2+
import Operadics
3+
4+
typealias Width = Int
5+
// TODO: What does this int mean, really
6+
typealias ColumnCount = Int
7+
typealias IndentLevel = Int
8+
/// The ribbon width is the maximal amount of non-indentation characters on a line
9+
typealias RibbonWidth = Int
10+
11+
indirect enum Doc {
12+
case empty
13+
/// Invariant: char != '\n'
14+
case _char(Character)
15+
/// Invariant: '\n' ∉ text
16+
case _text(length: Int, String)
17+
case _line
18+
/// If flattened then whenFlattened else primary
19+
case flatAlt(primary: Doc, whenFlattened: Doc)
20+
case concat(Doc, Doc)
21+
/// Renders Doc with an increased indent level
22+
/// Note: This only affects line after the first newline
23+
case nest(IndentLevel, Doc)
24+
/// Invariant: longerLines.count >= shorterLines.split('\n').first.count
25+
case union(longerLines: Doc, shorterLines: Doc)
26+
// No support for Annotations for now, I don't think Swift's generics would take kindly to a Doc<A>
27+
// case annotate(A, Doc)
28+
case column((ColumnCount) -> Doc)
29+
case nesting((IndentLevel) -> Doc)
30+
case columns((ColumnCount?) -> Doc)
31+
case ribbon((RibbonWidth?) -> Doc)
32+
33+
static func char(_ c: Character) -> Doc {
34+
return c == "\n" ? ._line : ._char(c)
35+
}
36+
37+
static func text(_ str: String) -> Doc {
38+
return str == "" ? .empty : ._text(length: str.characters.count, str)
39+
}
40+
41+
static var line: Doc {
42+
return .flatAlt(primary: ._line, whenFlattened: .space)
43+
}
44+
45+
static var linebreak: Doc {
46+
return .flatAlt(primary: ._line, whenFlattened: .zero)
47+
}
48+
49+
static var hardline: Doc {
50+
return ._line
51+
}
52+
53+
/// Used to specify alternative layouts
54+
/// `doc.grouped` removes all line breaks in `doc`. The resulting line
55+
/// is added if it fits on the page. If it doesn't, it's rendered as is
56+
var grouped: Doc {
57+
return .union(longerLines: flattened, shorterLines: self)
58+
}
59+
60+
var flattened: Doc {
61+
switch self {
62+
case .empty: return self
63+
case ._char(_): return self
64+
case ._text(length: _, _): return self
65+
case ._line: return self
66+
case let .flatAlt(_, whenFlattened): return whenFlattened
67+
case let .concat(x, y): return .concat(x.flattened, y.flattened)
68+
case let .nest(i, x): return .nest(i, x.flattened)
69+
case let .union(x, _): return x.flattened
70+
case let .column(f): return .column { f($0).flattened }
71+
case let .nesting(f): return .nesting { f($0).flattened }
72+
case let .columns(f): return .columns { f($0).flattened }
73+
case let .ribbon(f): return .ribbon { f($0).flattened }
74+
}
75+
}
76+
}
77+

Sources/DocFunctor.swift

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
//
2+
// DocFunctor.swift
3+
// DoctorPretty
4+
//
5+
// Created by Brandon Kase on 5/20/17.
6+
//
7+
//
8+
9+
// If we decide to add annotation support and make a Doc<A>
10+
/*extension Doc /*: Functor*/ {
11+
func fmap<B>(_ f: @escaping (A) -> B) -> Doc<B> {
12+
switch self {
13+
case .empty: return .empty
14+
case let ._char(c): return ._char(c)
15+
case let ._text(length, str): return ._text(length: length, str)
16+
case ._line: return ._line
17+
case let .flatAlt(primary, whenFlattened):
18+
return .flatAlt(primary: primary.fmap(f), whenFlattened: whenFlattened.fmap(f))
19+
case let .cat(d1, d2):
20+
return .cat(d1.fmap(f), d2.fmap(f))
21+
case let .nest(i, d):
22+
return .nest(i, d.fmap(f))
23+
case let .union(longerLines, shorterLines):
24+
return .union(longerLines: longerLines.fmap(f), shorterLines: shorterLines.fmap(f))
25+
case let .annotate(a, d):
26+
return .annotate(f(a), d.fmap(f))
27+
case let .column(g):
28+
return .column { g($0).fmap(f) }
29+
case let .nesting(g):
30+
return .nesting { g($0).fmap(f) }
31+
case let .columns(g):
32+
return .columns { g($0).fmap(f) }
33+
case let .ribbon(g):
34+
return .ribbon { g($0).fmap(f) }
35+
}
36+
}
37+
}*/

Sources/DocMonoid.swift

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
//
2+
// DocMonoid.swift
3+
// DoctorPretty
4+
//
5+
// Created by Brandon Kase on 5/20/17.
6+
//
7+
//
8+
9+
import Foundation
10+
import protocol Algebra.Additive
11+
import Operadics
12+
13+
extension Doc: Additive {
14+
static func <>(l: Doc, r: Doc) -> Doc {
15+
return .concat(l, r)
16+
}
17+
18+
static var zero: Doc { return .empty }
19+
}

Sources/Extras.swift

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
//
2+
// Extras.swift
3+
// DoctorPretty
4+
//
5+
// Created by Brandon Kase on 5/20/17.
6+
//
7+
//
8+
9+
import Foundation
10+
11+
func spaces(_ i: Int) -> String {
12+
return (0...i).map{ _ in " " }.joined()
13+
}

Sources/Fills.swift

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
//
2+
// Fills.swift
3+
// DoctorPretty
4+
//
5+
// Created by Brandon Kase on 5/20/17.
6+
//
7+
//
8+
9+
import Foundation
10+
import Operadics
11+
12+
extension Doc {
13+
/// Render doc then fill until width == i
14+
/// If too large, put in a line-break and then pad
15+
func fillBreak(_ i: IndentLevel) -> Doc {
16+
return width { w in w > i ? .nest(i, .linebreak) : .text(spaces(i - w)) }
17+
}
18+
19+
/// Render doc then fill until width == i
20+
func fill(_ i: IndentLevel) -> Doc {
21+
return width { w in w >= i ? .zero : .text(spaces(i - w)) }
22+
}
23+
24+
func width(_ f: @escaping (IndentLevel) -> Doc) -> Doc {
25+
return .column { k1 in self <> .column { k2 in f(k2 - k1) } }
26+
}
27+
}

0 commit comments

Comments
 (0)