Source file nest.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
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
open Stylesheet
let contains sel =
Selector.any (function Selector.Nesting -> true | _ -> false) sel
let complex = function
| Selector.List _ | Selector.Combined _ | Selector.Relative _ -> true
| _ -> false
let count_nesting sel =
let n = ref 0 in
ignore
(Selector.any
(fun s ->
(match s with Selector.Nesting -> incr n | _ -> ());
false)
sel);
!n
let rec heads = function
| Selector.Nesting -> true
| Selector.Compound (x :: _) -> heads x
| Selector.Combined (l, _, _) -> heads l
| _ -> false
let is_list = function Selector.List _ -> true | _ -> false
let one_weight = function
| Selector.List (branch :: rest) ->
let weight = Selector.specificity branch in
List.for_all
(fun b -> Selector.equal_specificity (Selector.specificity b) weight)
rest
| _ -> true
let splices_bare ~leftmost ~parent sel =
if is_list parent then
match sel with Selector.Nesting -> one_weight parent | _ -> false
else leftmost && heads sel && count_nesting sel = 1
let rec leads_compound = function
| Selector.Element _ | Selector.Universal _ -> true
| Selector.Compound (x :: _) -> leads_compound x
| _ -> false
let nesting_leads_every_compound sel =
let rec walk ~leading = function
| Selector.Nesting -> leading
| Selector.Compound (x :: rest) ->
walk ~leading x
&& List.for_all
(fun s ->
not
(Selector.any
(function Selector.Nesting -> true | _ -> false)
s))
rest
| Selector.Combined (l, _, r) ->
walk ~leading:true l && walk ~leading:true r
| Selector.Relative (_, r) -> walk ~leading:true r
| Selector.List branches -> List.for_all (walk ~leading:true) branches
| s ->
not (Selector.any (function Selector.Nesting -> true | _ -> false) s)
in
walk ~leading:true sel
let substitute ?(leftmost = true) ~parent sel =
let verbatim =
((not (complex parent)) || splices_bare ~leftmost ~parent sel)
&& ((not (leads_compound parent)) || nesting_leads_every_compound sel)
in
let parent = if verbatim then parent else Selector.Is [ parent ] in
Selector.map (function Selector.Nesting -> parent | s -> s) sel
let grouped parent = if is_list parent then Selector.Is [ parent ] else parent
let rec combine parent child =
match child with
| Selector.List branches -> Selector.List (List.map (combine parent) branches)
| Selector.Relative (comb, right) ->
Selector.Combined
(grouped parent, comb, substitute ~leftmost:false ~parent right)
| _ when contains child -> substitute ~parent child
| _ -> Selector.Combined (grouped parent, Selector.Descendant, child)
let keep_readable_branches (selector : Selector.t) =
let keeps sel =
(not (Selector.has_combinator_after_pseudo_element sel))
&& not (Selector.has_refused_simple_in_compound sel)
in
match selector with
| Selector.List branches -> (
match List.filter keeps branches with
| [] -> Option.None
| [ branch ] -> Option.Some branch
| branches -> Option.Some (Selector.List branches))
| sel when keeps sel -> Option.Some sel
| _ -> Option.None
let rec merge_lone (rule : rule) =
match (rule.declarations, rule.nested) with
| [], [ Rule child ] when not (is_list rule.selector) -> (
match keep_readable_branches (combine rule.selector child.selector) with
| Option.Some selector -> merge_lone { child with selector }
| Option.None -> { rule with nested = [] })
| _ -> rule
let live_under parent branch =
Option.is_some (keep_readable_branches (combine parent branch))
let live_branches parent (selector : Selector.t) =
match selector with
| Selector.List branches -> (
match Common.List.filter_preserve (live_under parent) branches with
| kept when kept == branches -> Option.Some selector
| [] -> Option.None
| [ branch ] -> Option.Some branch
| kept -> Option.Some (Selector.List kept))
| sel -> if live_under parent sel then Option.Some sel else Option.None
let under_pseudo_element sel = Selector.any Selector.is_pseudo_element sel
let rec live_statements parent (stmts : statement list) =
Common.List.filter_map_preserve (live_statement parent) stmts
and live_statement parent (stmt : statement) =
match stmt with
| Rule child -> (
match live_branches parent child.selector with
| Option.None -> Option.None
| Option.Some selector ->
let nested = live_statements (combine parent selector) child.nested in
if selector == child.selector && nested == child.nested then
Option.Some stmt
else Option.Some (Rule { child with selector; nested }))
| stmt -> Option.Some (map_statement_children (live_statements parent) stmt)
let drop_dead_nested (rule : rule) =
match rule.nested with
| [] -> rule
| _ when not (under_pseudo_element rule.selector) -> rule
| body ->
let nested = live_statements rule.selector body in
if nested == body then rule else { rule with nested }
type barrier = Opaque | Crossed of Declaration.declaration list
let rec crossing barrier (stmts : statement list) =
match (barrier, stmts) with
| Opaque, _ | _, [] -> barrier
| Crossed decls, stmt :: rest ->
let barrier =
match stmt with
| Unknown_at_rule _ -> Opaque
| _ ->
crossing
(Crossed (List.rev_append (statement_declarations stmt) decls))
(statement_children stmt)
in
crossing barrier rest
let crosses_freely barrier decl =
match barrier with
| Opaque -> false
| Crossed decls -> Shorthand.declarations_commute [ decl ] decls
let hoist_declaration_runs (rule : rule) =
let hoist_run barrier run =
List.fold_left
(fun (hoisted, barrier, kept) decl ->
if crosses_freely barrier decl then (decl :: hoisted, barrier, kept)
else
let barrier =
match barrier with
| Opaque -> Opaque
| Crossed decls -> Crossed (decl :: decls)
in
(hoisted, barrier, decl :: kept))
([], barrier, []) run
in
let rec go hoisted barrier nested = function
| [] -> (List.rev hoisted, List.rev nested)
| Declarations run :: rest ->
let run_hoisted, barrier, kept = hoist_run barrier run in
let nested =
match List.rev kept with
| [] -> nested
| kept -> Declarations kept :: nested
in
go (run_hoisted @ hoisted) barrier nested rest
| stmt :: rest ->
go hoisted (crossing barrier [ stmt ]) (stmt :: nested) rest
in
match go [] (Crossed []) [] rule.nested with
| [], _ -> rule
| hoisted, nested ->
{ rule with declarations = rule.declarations @ hoisted; nested }
let rec strip_prefix (parent : Selector.t) (child : Selector.t) =
match (parent, child) with
| _, Selector.Combined (cp, comb, crest) when Selector.equal cp parent ->
Some
(if comb = Selector.Descendant then crest
else Selector.Relative (comb, crest))
| Selector.Combined (pp, pcomb, prest), Selector.Combined (cp, ccomb, crest)
when Selector.equal_combinator pcomb ccomb && Selector.equal pp cp ->
strip_prefix prest crest
| _, Selector.Compound cps -> (
let pps =
match parent with Selector.Compound l -> l | single -> [ single ]
in
let rec drop p c =
match (p, c) with
| [], rest -> Some rest
| ph :: pt, ch :: ct when ph = ch -> drop pt ct
| _ -> None
in
match drop pps cps with
| Some (_ :: _ as suffix) ->
Some (Selector.Compound (Selector.Nesting :: suffix))
| _ -> None)
| _ -> None
let extends a b = strip_prefix a b <> None
let identifying_components sel =
let acc = ref [] in
ignore
(Selector.any
(fun s ->
(match s with
| Selector.Class _ | Selector.Id _
| Selector.Element (_, _)
| Selector.Attribute _ ->
acc := s :: !acc
| _ -> ());
false)
sel);
!acc
let compete a b =
let ca = identifying_components a in
List.exists (fun t -> List.mem t (identifying_components b)) ca
let rec chain (root : rule) = function
| [] -> root
| (child : rule) :: rest -> (
match strip_prefix root.selector child.selector with
| Some rel ->
let nested_child = chain child rest in
let nested_child = { nested_child with selector = rel } in
{ root with nested = root.nested @ [ Rule nested_child ] }
| None -> root)
let shortens before after =
let len rules =
List.fold_left
(fun acc r -> acc + Pp.size ~minify:true Stylesheet.pp_rule r)
0 rules
in
len [ after ] < len before
open Common
let preserve = List.preserve
let rules (rules : rule list) =
let arr : rule array = Array.of_list rules in
let n = Array.length arr in
let chains = ref [] in
let i = ref 0 in
while !i < n do
let start = !i in
incr i;
while !i < n && extends arr.(!i - 1).selector arr.(!i).selector do
incr i
done;
chains := (start, !i - start) :: !chains
done;
let chains = List.rev !chains in
let isolated start len =
let members = Array.sub arr start len in
let related_outside =
Array.to_list arr
|> List.mapi (fun idx r -> (idx, r))
|> List.exists (fun (idx, (r : rule)) ->
(idx < start || idx >= start + len)
&& Array.exists
(fun (m : rule) -> compete m.selector r.selector)
members)
in
not related_outside
in
let rules' =
List.concat_map
(fun (start, len) ->
let members = Array.to_list (Array.sub arr start len) in
match members with
| root :: (_ :: _ as rest)
when (not (is_list root.selector)) && isolated start len ->
let nested = chain root rest in
if shortens members nested then [ nested ] else members
| _ -> members)
chains
in
preserve rules rules'
let statements (stmts : statement list) =
let rec should_try count = function
| [] -> count <= 128
| Rule r :: _ when r.Stylesheet_intf.nested <> [] -> true
| Rule _ :: rest when count < 128 -> should_try (count + 1) rest
| Rule _ :: _ -> false
| _ :: rest -> should_try count rest
in
if not (should_try 0 stmts) then stmts
else
let rec span stmt_acc rule_acc = function
| (Rule r as stmt) :: rest -> span (stmt :: stmt_acc) (r :: rule_acc) rest
| rest -> (List.rev stmt_acc, List.rev rule_acc, rest)
in
let rec go acc = function
| [] -> List.rev acc
| Rule _ :: _ as l ->
let stmts, rule_list, rest = span [] [] l in
let rules' = rules rule_list in
let synthesized =
if rules' == rule_list then stmts
else List.map (fun r -> Rule r) rules'
in
go (List.rev_append synthesized acc) rest
| s :: rest -> go (s :: acc) rest
in
preserve stmts (go [] stmts)