-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathprettiest.ml
290 lines (271 loc) · 7.47 KB
/
prettiest.ml
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
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
let (%) f g x = f (g x)
type ('a,'b) either = Left of 'a | Right of 'b
let partition_eithers xs =
let rec go ls rs = function
| [] ->
List.rev ls, List.rev rs
| x::xs ->
match x with
| Left l ->
go (l::ls) rs xs
| Right r ->
go ls (r::rs) xs
in
go [] [] xs
module LazyList = struct
type 'a node = Nil | Cons of 'a * 'a t
and 'a t = 'a node Lazy.t
let empty = lazy Nil
let singleton x = lazy (Cons (x, empty))
let force = Lazy.force
let rec map f l = lazy (
match force l with
| Nil -> Nil
| Cons (h, t) -> Cons (f h, map f t)
)
let rec append l1 l2 = lazy (
match force l1 with
| Nil -> force l2
| Cons (h, t) -> Cons (h, append t l2)
)
let rec concat ll = lazy (
match force ll with
| Nil -> Nil
| Cons (h, t) -> append h (concat t) |> force
)
let is_empty l = force l = Nil
end
let force = Lazy.force
type doc =
| Empty
| Line of bool
| Nest of int * doc
| Char of char
| Text of string
| Cat of doc * doc
| Union of doc * doc
| Column of (int -> doc)
| Nesting of (int -> doc)
| MaxColumn of int
type sdoc =
| SEmpty
| SChar of char * sdoc Lazy.t
| SText of string * sdoc Lazy.t
| SLine of int * sdoc Lazy.t
type idoc =
| IEmpty
| IChar of char * idoc
| IText of string * idoc
| ILine of int * idoc
let rec flatten x =
match x with
| Empty
| Char _
| Text _
| MaxColumn _ -> x
| Line false -> Empty
| Line true -> Char ' '
| Nest (_, x) -> flatten x
| Cat (x, y) -> Cat (flatten x, flatten y)
| Union (x, y) -> flatten x
| Column f -> Column (flatten % f)
| Nesting f -> Nesting (flatten % f)
let group x = Union (flatten x, x)
let (<|>) x y = Union (x, y)
let (<.>) x y = Cat (x, y)
let space = Text " "
let (<+>) x y = Cat (x, Cat (space, y))
let (</>) x y = Cat (x, Cat (group (Line true), y))
let (<//>) x y = Cat (x, Cat (group (Line false), y))
let (<+/>) x y = Cat (x, Cat (Union (space, Line true), y))
let (<$>) x y = Cat (x, Cat (Line true, y))
let align x = Column (fun k -> Nesting (fun i -> Nest (k-i, x)))
let nest i x = Nest(i, x)
let int i = Text (string_of_int i)
let intw w i =
let s = string_of_int i in
let l = String.length s in
if w > l then
Text (String.make (w-l) ' ' ^ s)
else
Text s
let width x f = Column (fun k1 -> x <.> Column (fun k2 -> f (k2-k1)))
let vsep = List.fold_left (<$>)
let empty = Empty
let char c = Char c
let text t = Text t
let space = Char ' '
let line = Line true
let linebreak = Line false
let softline = group line
let softbreak = group linebreak
let lbrace = Char '{'
let rbrace = Char '}'
let lbracket = Char '['
let rbracket = Char ']'
let langle = Char '<'
let rangle = Char '>'
let lparen = Char '('
let rparen = Char ')'
let semicolon = Char ';'
let comma = Char ','
let fill_break f x =
width x (fun w ->
if w > f then Nest (f, Line false)
else Text (String.make (f-w) ' '))
let fill f x =
width x (fun w ->
if w >= f then Empty
else Text (String.make (f-w) ' '))
let rec sep_by sep = function
| [] -> Empty
| x::xs ->
let rec go acc = function
| [] -> acc
| x::xs -> go (acc <.> sep <.> x) xs
in
go x xs
let rec sep_by' sep xs =
align @@ sep_by sep xs
let enclose l r x = l <.> x <.> r
let fill_sep xs =
match xs with
| [] -> Empty
| x::xs -> List.fold_left (</>) x xs
let rec enclose_sep l r sep = function
| [] -> l <.> r
| x::xs ->
let rec go acc = function
| [] -> acc
| x::xs -> go (acc <.> sep <.> x) xs
in
align (go (l <.> x) xs <.> r)
let rec enclose_sep_a l r sep a =
let n = Array.length a in
if n = 0 then
l <.> r
else
let rec go acc i =
if i = n then
acc
else
go (acc <.> sep <.> a.(i)) (i+1)
in
align (go (l <.> a.(0)) 1 <.> r)
let render_greedy rfrac w x =
let r = rfrac *. float_of_int w |> int_of_float |> min w |> max 0 in
let better n k x y =
let rec fits w = function
| _ when w < 0 -> false
| SEmpty -> true
| SChar (c, x) -> fits (w-1) (force x)
| SText (s, x) -> fits (w-String.length s) (force x)
| SLine (i, x) -> true
in
if fits (min (w-k) (r-k+n)) x then x else y
in
let rec best n k = function
| [] -> SEmpty
| (i,d)::ds ->
match d with
| Empty -> best n k ds
| Line _ -> SLine (i, lazy (best i i ds))
| Nest (j,x) -> best n k ((i+j,x)::ds)
| Char c -> SChar (c, lazy (best n (k+1) ds))
| Text s -> SText (s, lazy (best n (k+String.length s) ds))
| Cat (x,y) -> best n k ((i,x)::(i,y)::ds)
| Union (x,y) -> better n k (best n k ((i,x)::ds)) (best n k ((i,y)::ds))
| Column f -> best n k ((i,f k)::ds)
| Nesting f -> best n k ((i,f i)::ds)
| MaxColumn w -> best n k ds (* ignore *)
in
let buf = Buffer.create 0 in
let rec out = function
| SEmpty ->
Buffer.contents buf
| SChar (c, x) ->
Buffer.add_char buf c;
out (force x)
| SText (s, x) ->
Buffer.add_string buf s;
out (force x)
| SLine (i, x) ->
Buffer.add_char buf '\n';
Buffer.add_string buf (String.make i ' ');
out (force x)
in
best 0 0 [0,x] |> out
let render rfrac w x =
let r = rfrac *. float_of_int w |> int_of_float |> min w |> max 0 in
let rec step b p n k inv ds =
if (w < k || r < k-n) && not b then
[]
else match ds with
| [] -> [Right inv]
| (i,d)::ds ->
match d with
| Empty -> step b p n k inv ds
| Line _ -> [Left (p,i,i,ILine (i,inv),ds)]
| Nest (j,x) -> step b p n k inv ((i+j,x)::ds)
| Char c -> step b (p+1) n (k+1) (IChar (c,inv)) ds
| Text s -> step b (p+1) n (k+String.length s) (IText (s,inv)) ds
| Cat (x,y) -> step b p n k inv ((i,x)::(i,y)::ds)
| Union (x,y) -> step b p n k inv ((i,x)::ds) @
step false p n k inv ((i,y)::ds)
| Column f -> step b (p+1) n k inv ((i,f k)::ds)
| Nesting f -> step b (p+1) n k inv ((i,f i)::ds)
| MaxColumn w -> if w < k && not b then [] else step b (p+1) n k inv ds
in
let rec filter xs =
let rec go p acc = function
| ((p',_,_,_,_) as x)::xs ->
if p >= p' then
go p acc xs
else
go p' (x::acc) xs
| [] ->
List.rev acc
in
match xs with
| [] -> []
| ((p,_,_,_,_) as x)::xs -> go p [x] xs
in
let rec loop frontier =
let ps, dones = List.map (fun (p,n,k,inv,r) ->
let z = step false p n k inv r in
if z = [] then step true p n k inv r else z) frontier |>
List.concat |> partition_eithers in
match dones with
| don::_ ->
don
| [] ->
if ps = [] then
IEmpty
else
List.sort (fun (p1,n1,_,_,_) (p2,n2,_,_,_) ->
if n1 <> n2 then n1-n2
else p2-p1
) ps |> filter |> loop
in
let rec invert acc = function
| IEmpty -> acc
| IChar (c, x) -> invert (IChar (c, acc)) x
| IText (s, x) -> invert (IText (s, acc)) x
| ILine (i, x) -> invert (ILine (i, acc)) x
in
let buf = Buffer.create 0 in
let rec out = function
| IEmpty ->
Buffer.contents buf
| IChar (c, x) ->
Buffer.add_char buf c;
out x
| IText (s, x) ->
Buffer.add_string buf s;
out x
| ILine (i, x) ->
Buffer.add_char buf '\n';
Buffer.add_string buf (String.make i ' ');
out x
in
loop [0,0,0,IEmpty,[0,x]] |> invert IEmpty |> out