Skip to content

Commit bf8d709

Browse files
authored
Added Swift code test; vastly improved perf (#2)
1 parent 78f6565 commit bf8d709

File tree

7 files changed

+237
-55
lines changed

7 files changed

+237
-55
lines changed

Sources/Atoms.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ extension Doc {
5353
static let space: Doc = .char(" ")
5454
static let dot: Doc = .char(".")
5555
static let comma: Doc = .char(",")
56+
static let semi: Doc = .char(";")
5657
static let backslash: Doc = .char("\\")
5758
static let equals: Doc = .char("=")
5859
}

Sources/DocMonoid.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,4 @@ extension Doc: Additive {
1717

1818
static var zero: Doc { return .empty }
1919
}
20+

Sources/Enclosed.swift

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
//
2+
// Enclosed.swift
3+
// DoctorPretty
4+
//
5+
// Created by Brandon Kase on 5/21/17.
6+
//
7+
//
8+
9+
import Foundation
10+
import Operadics
11+
12+
extension BidirectionalCollection where Iterator.Element == Doc, IndexDistance == Int, SubSequence.Iterator.Element == Doc {
13+
14+
/// Intersperses punctuation inside docs
15+
func punctuate(with punctuation: Doc) -> [Doc] {
16+
if let d = first {
17+
return [d] + self.dropFirst().reduce([]) { acc, d2 in
18+
acc <> [punctuation, d2]
19+
}
20+
} else {
21+
return []
22+
}
23+
}
24+
25+
/// Enclose left right around docs interspersed with separator
26+
/// Tries horizontal, then tries vertical
27+
/// Indenting each element in the list by the indent level
28+
/// Ex:
29+
/// enclose(left: [, right: ], separator: comma, indent: 4)
30+
/// let x = [foo, bar, baz]
31+
/// or
32+
/// let x = [
33+
/// foo,
34+
/// bar,
35+
/// baz
36+
/// ]
37+
/// Note: The Haskell version sticks the separator at the front
38+
func enclose(left: Doc, right: Doc, separator: Doc, indent: IndentLevel) -> Doc {
39+
if count == 0 {
40+
return left <> right
41+
}
42+
43+
let seps = repeatElement(separator, count: count-1)
44+
let last = self[self.index(before: self.endIndex)]
45+
let punctuated = zip(self.dropLast(), seps).map(<>) <> [last]
46+
return (
47+
.nest(indent,
48+
left <&&> punctuated.sep()
49+
) <&&> right
50+
).grouped
51+
}
52+
53+
/// See @enclose
54+
func list(indent: IndentLevel) -> Doc {
55+
return enclose(left: Doc.lbracket, right: Doc.rbracket, separator: Doc.comma, indent: indent)
56+
}
57+
58+
/// See @enclose
59+
func tupled(indent: IndentLevel) -> Doc {
60+
return enclose(left: Doc.lparen, right: Doc.rparen, separator: Doc.comma, indent: indent)
61+
}
62+
63+
/// See @enclose
64+
func semiBraces(indent: IndentLevel) -> Doc {
65+
return enclose(left: Doc.lbrace, right: Doc.rbrace, separator: Doc.semi, indent: indent)
66+
}
67+
}

Sources/HighLevelCombinators.swift

Lines changed: 22 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -44,66 +44,66 @@ extension Doc {
4444
return Doc.linebreak.grouped
4545
}
4646

47-
/// Puts a line between x and y that can be flattened
47+
/// Puts a line between x and y that can be flattened to a space
4848
static func <&>(x: Doc, y: Doc) -> Doc {
4949
return x <> .line <> y
5050
}
5151

52-
/// Puts a line between x and y undconditionally
52+
/// Puts a line between x and y that can be flattened with no space
5353
static func <&&>(x: Doc, y: Doc) -> Doc {
5454
return x <> .linebreak <> y
5555
}
5656
}
5757

58-
extension Doc {
58+
extension Sequence where Iterator.Element == Doc {
5959
/// Concat all horizontally if it fits, but if not
6060
/// all vertical
61-
static func sep<S: Sequence>(_ docs: S) -> Doc where S.Iterator.Element == Doc {
62-
return vsep(docs).grouped
61+
func sep() -> Doc {
62+
return vsep().grouped
6363
}
6464

6565
/// Concats all horizontally until end of page
6666
/// then puts a line and repeats
67-
static func fillSep<S: Sequence>(_ docs: S) -> Doc where S.Iterator.Element == Doc {
68-
return fold(docs: docs, combine: <%>)
67+
func fillSep() -> Doc {
68+
return fold(combineDocs: <%>)
6969
}
7070

7171
/// Concats all horizontally with spaces in between
72-
static func hsep<S: Sequence>(_ docs: S) -> Doc where S.Iterator.Element == Doc {
73-
return fold(docs: docs, combine: <+>)
72+
func hsep() -> Doc {
73+
return fold(combineDocs: <+>)
7474
}
7575

7676
/// Concats all vertically, if a group undoes, concat with space
77-
static func vsep<S: Sequence>(_ docs: S) -> Doc where S.Iterator.Element == Doc {
78-
return fold(docs: docs, combine: <&>)
77+
func vsep() -> Doc {
78+
return fold(combineDocs: <&>)
7979
}
8080

8181
/// Concats all horizontally no spaces if fits
8282
/// Otherwise all vertically
83-
static func cat<S: Sequence>(_ docs: S) -> Doc where S.Iterator.Element == Doc {
84-
return vcat(docs).grouped
83+
func cat() -> Doc {
84+
return vcat().grouped
8585
}
8686

8787
/// Concats all horizontally until end of page
8888
/// then puts a linebreak and repeats
89-
static func fillCat<S: Sequence>(_ docs: S) -> Doc where S.Iterator.Element == Doc {
90-
return fold(docs: docs, combine: <%%>)
89+
func fillCat() -> Doc {
90+
return fold(combineDocs: <%%>)
9191
}
9292

9393
/// Concats all horizontally with no spaces
94-
static func hcat<S: Sequence>(_ docs: S) -> Doc where S.Iterator.Element == Doc {
95-
return fold(docs: docs, combine: <>)
94+
func hcat() -> Doc {
95+
return fold(combineDocs: <>)
9696
}
9797

9898
/// Concats all vertically, if a group undoes, concat with no space
99-
static func vcat<S: Sequence>(_ docs: S) -> Doc where S.Iterator.Element == Doc {
100-
return fold(docs: docs, combine: <&&>)
99+
func vcat() -> Doc {
100+
return fold(combineDocs: <&&>)
101101
}
102102

103-
static func fold<S: Sequence>(docs: S, combine: (Doc, Doc) -> Doc) -> Doc where S.Iterator.Element == Doc {
104-
var iter = docs.makeIterator()
103+
func fold(combineDocs: (Doc, Doc) -> Doc) -> Doc {
104+
var iter = makeIterator()
105105
if let first = iter.next() {
106-
return IteratorSequence(iter).reduce(first, combine)
106+
return IteratorSequence(iter).reduce(first, combineDocs)
107107
} else {
108108
return Doc.zero
109109
}

Sources/RenderPretty.swift

Lines changed: 26 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,22 @@
88

99
import Foundation
1010

11-
typealias CompareStrategy = (IndentLevel, ColumnCount, Width, RibbonWidth) -> (SimpleDoc, SimpleDoc) -> SimpleDoc
11+
typealias CompareStrategy = (IndentLevel, ColumnCount, Width, RibbonWidth) -> (SimpleDoc, () -> SimpleDoc) -> SimpleDoc
12+
13+
indirect enum List<T> {
14+
case Nil
15+
case Cons(T, List<T>)
16+
}
17+
1218
// TODO: Tune this datastructure to increase render performance
1319
// Good properties:
1420
// fast prepend
1521
// fast (head, rest) decomposition
1622
// shared substructure
1723
// The obvious choice is a linked list, but maybe a clever sequence
18-
// could be better
19-
typealias Docs = [(Int, Doc)]
24+
// could be better. Also, perhaps not using the enum-list will speed
25+
// things up more.
26+
typealias Docs = List<(Int, Doc)>
2027

2128
extension Doc {
2229
func renderPrettyDefault() -> SimpleDoc {
@@ -40,9 +47,10 @@ extension Doc {
4047
z: (IndentLevel, ColumnCount) -> SimpleDoc,
4148
indentationDocs: Docs
4249
) -> SimpleDoc {
43-
if let head = indentationDocs.first {
50+
switch indentationDocs {
51+
case .Nil: return z(currNesting, currColumn)
52+
case let .Cons(head, rest):
4453
let (indent, doc) = head
45-
let rest = Array(indentationDocs.dropFirst())
4654

4755
switch doc {
4856
case .empty:
@@ -56,41 +64,40 @@ extension Doc {
5664
case ._line:
5765
return SimpleDoc.line(indent: indent, best(currNesting: indent, currColumn: indent, z: z, indentationDocs: rest))
5866
case let .flatAlt(primary, whenFlattened: _):
59-
return best(currNesting: currNesting, currColumn: currColumn, z: z, indentationDocs: [(indent, primary)] + rest)
67+
return best(currNesting: currNesting, currColumn: currColumn, z: z, indentationDocs: .Cons((indent, primary), rest))
6068
case let .concat(d1, d2):
61-
return best(currNesting: currNesting, currColumn: currColumn, z: z, indentationDocs: [(indent, d1), (indent, d2)] + rest)
69+
return best(currNesting: currNesting, currColumn: currColumn, z: z, indentationDocs: .Cons((indent, d1), .Cons((indent, d2), rest)))
6270
case let .nest(indent_, d):
6371
let newIndent = indent + indent_
64-
return best(currNesting: currNesting, currColumn: currColumn, z: z, indentationDocs: [(newIndent, d)] + rest)
72+
return best(currNesting: currNesting, currColumn: currColumn, z: z, indentationDocs: .Cons((newIndent, d), rest))
6573
case let .union(longerLines, shorterLines):
6674
return compareStrategy(
6775
currNesting,
6876
currColumn,
6977
pageWidth,
7078
ribbonChars
7179
)(
72-
best(currNesting: currNesting, currColumn: currColumn, z: z, indentationDocs: [(indent, longerLines)] + rest),
73-
best(currNesting: currNesting, currColumn: currColumn, z: z, indentationDocs: [(indent, shorterLines)] + rest)
80+
best(currNesting: currNesting, currColumn: currColumn, z: z, indentationDocs: .Cons((indent, longerLines), rest)),
81+
/// Laziness is needed here to prevent horrible performance!
82+
{ () in best(currNesting: currNesting, currColumn: currColumn, z: z, indentationDocs: .Cons((indent, shorterLines), rest)) }
7483
)
7584
case let .column(f):
76-
return best(currNesting: currNesting, currColumn: currColumn, z: z, indentationDocs: [(indent, f(currColumn))] + rest)
85+
return best(currNesting: currNesting, currColumn: currColumn, z: z, indentationDocs: .Cons((indent, f(currColumn)), rest))
7786
case let .nesting(f):
78-
return best(currNesting: currNesting, currColumn: currColumn, z: z, indentationDocs: [(indent, f(indent))] + rest)
87+
return best(currNesting: currNesting, currColumn: currColumn, z: z, indentationDocs: .Cons((indent, f(indent)), rest))
7988
case let .columns(f):
80-
return best(currNesting: currNesting, currColumn: currColumn, z: z, indentationDocs: [(indent, f(.some(pageWidth)))] + rest)
89+
return best(currNesting: currNesting, currColumn: currColumn, z: z, indentationDocs: .Cons((indent, f(.some(pageWidth))), rest))
8190
case let .ribbon(f):
82-
return best(currNesting: currNesting, currColumn: currColumn, z: z, indentationDocs: [(indent, f(.some(ribbonChars)))] + rest)
91+
return best(currNesting: currNesting, currColumn: currColumn, z: z, indentationDocs: .Cons((indent, f(.some(ribbonChars))), rest))
8392
}
84-
} else {
85-
return z(currNesting, currColumn)
8693
}
8794
}
8895

89-
return best(currNesting: 0, currColumn: 0, z: { _, _ in SimpleDoc.empty }, indentationDocs: [(0, self)])
96+
return best(currNesting: 0, currColumn: 0, z: { _, _ in SimpleDoc.empty }, indentationDocs: .Cons((0, self), .Nil))
9097
}
9198

9299
/// Compares the first two lines of the documents
93-
static func nicest1(nesting: IndentLevel, column: ColumnCount, pageWidth: Width, ribbonWidth: RibbonWidth) -> (SimpleDoc, SimpleDoc) -> SimpleDoc {
100+
static func nicest1(nesting: IndentLevel, column: ColumnCount, pageWidth: Width, ribbonWidth: RibbonWidth) -> (SimpleDoc, () -> SimpleDoc) -> SimpleDoc {
94101
return { d1, d2 in
95102
let wid = min(pageWidth - column, ribbonWidth - column + nesting)
96103

@@ -109,7 +116,7 @@ extension Doc {
109116
if fits(prefix: min(nesting, column), w: wid, doc: d1) {
110117
return d1
111118
} else {
112-
return d2
119+
return d2()
113120
}
114121
}
115122
}

Tests/DoctorPrettyTests/DoctorPrettySpec.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ extension Doc: Arbitrary {
3030
(3, stringDocGen),
3131
(1, lineOrEmptyGen)
3232
]).proliferate(withSize: 10)
33-
.map{ ds in Doc.cat(ds) }
33+
.map{ $0.cat() }
3434
}
3535
}
3636

0 commit comments

Comments
 (0)