-
Notifications
You must be signed in to change notification settings - Fork 8
/
Document.scala
137 lines (111 loc) · 3.84 KB
/
Document.scala
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
/**
* Lightweight library from the deprecated scala.text distribution.
*/
package fuselang.common
import java.io.Writer
object PrettyPrint:
case object DocNil extends Doc
case object DocBreak extends Doc
case object DocSpace extends Doc
case class DocText(txt: String) extends Doc
case class DocNest(indent: Int, doc: Doc) extends Doc
case class DocCons(hd: Doc, tl: Doc) extends Doc {}
/**
* A basic pretty-printing library, based on Lindig's strict version
* of Wadler's adaptation of Hughes' pretty-printer.
*
* @author Michel Schinz
* @version 1.0
*/
abstract class Doc:
def <@>(hd: Doc): Doc =
if hd == DocNil then this
else this <> DocBreak <> hd
def <>(hd: Doc): Doc = (this, hd) match
case (_, DocNil) => this
case (DocNil, _) => hd
case _ => new DocCons(this, hd)
def <+>(hd: Doc): Doc = this <> DocSpace <> hd
def pretty: String =
val writer = new java.io.StringWriter()
format(writer)
writer.toString
/**
* Format this Doc on `writer`.
*/
def format(writer: Writer): Unit =
type FmtState = (Int, Doc)
def spaces(n: Int): Unit =
var rem = n
while rem >= 16 do { writer write " "; rem -= 16 }
if rem >= 8 then { writer write " "; rem -= 8 }
if rem >= 4 then { writer write " "; rem -= 4 }
if rem >= 2 then { writer write " "; rem -= 2 }
if rem == 1 then { writer write " " }
def fmt(state: List[FmtState]): Unit = state match
case List() => ()
case (_, DocNil) :: z => fmt(z)
case (i, DocCons(h, t)) :: z => fmt((i, h) :: (i, t) :: z)
case (_, DocText(t)) :: z => {
writer.write(t)
fmt(z)
}
case (i, DocNest(ii, d)) :: z => fmt((i + ii, d) :: z)
case (i, DocBreak) :: z => {
writer.write("\n")
spaces(i)
fmt(z)
}
case (_, DocSpace) :: z => {
writer.write(" "); fmt(z)
}
case _ => ()
fmt(List((0, this)))
object Doc:
/** The empty Doc */
def emptyDoc = DocNil
def line = DocBreak
def space = DocSpace
/** A Doc consisting of some text literal */
def text(s: String): Doc = DocText(s)
def value(v: Any): Doc = text(v.toString)
/** Common primitives */
def semi: Doc = text(";")
def colon: Doc = text(":")
def comma: Doc = text(",")
def dot: Doc = text(".")
def equal: Doc = text("=")
def lbrace: Doc = text("{")
def rbrace: Doc = text("}")
/** A nested Doc, which will be indented as specified. */
def nest(d: Doc, i: Int): Doc = DocNest(i, d)
def folddoc(ds: Iterable[Doc], f: (Doc, Doc) => Doc) =
if ds.isEmpty then emptyDoc
else ds.tail.foldLeft(ds.head)(f)
/** Builder functions */
def hsep(ds: Iterable[Doc], sep: Doc): Doc =
folddoc(ds, (_ <> sep <> _))
def ssep(ds: Iterable[Doc], sep: Doc): Doc =
folddoc(ds, (_ <> sep <> _))
def hsep(ds: Iterable[Doc]): Doc =
folddoc(ds, (_ <+> _))
def vsep(ds: Iterable[Doc], sep: Doc): Doc =
folddoc(ds, (_ <@> sep <@> _))
def vsep(ds: Iterable[Doc]): Doc =
folddoc(ds, (_ <@> _))
def enclose(l: Doc, d: Doc, r: Doc) = l <> d <> r
def surround(d: Doc, s: Doc) = enclose(s, d, s)
def commaSep(docs: Seq[Doc]) = hsep(docs, comma <> space)
def scope(
doc: Doc,
left: Doc = lbrace,
right: Doc = rbrace,
indent: Int = 2,
): Doc =
left <> nest(emptyDoc <@> doc, indent) <@> right
/** Common functions **/
def quote(d: Doc) = surround(d, text("\""))
def parens(d: Doc) = enclose(text("("), d, text(")"))
def braces(d: Doc) = enclose(text("{"), d, text("}"))
def brackets(d: Doc) = enclose(text("["), d, text("]"))
def angles(d: Doc) = enclose(text("<"), d, text(">"))