Source file process.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
let invalid_arg fmt = Format.kasprintf invalid_arg fmt
let reword_error f = function Ok _ as v -> v | Error err -> Error (f err)
open Sigs
let process_fmt :
type test v. Metadata.t -> (test, v) Ty.t -> v Tree.fmt -> v -> Metadata.t =
fun m t tree_fmt v ->
match t with
| Ty.Clear -> Metadata.clear m
| _ ->
let buf = Buffer.create 16 in
let ppf = Format.formatter_of_buffer buf in
Fmt.keval Pps.v ppf
Fmt.([ String Nop ] ^^ Tree.fmt tree_fmt ())
(fun ppf ->
Format.fprintf ppf "%!";
Metadata.with_output (Buffer.contents buf) m)
(Option.value ~default:"" (Metadata.output m))
v
let process :
type s fd error.
s scheduler ->
(fd, error, s) syscall ->
fd ->
int64 ->
Metadata.t ->
Tree.operation ->
( ( int64 * Metadata.t,
[> `Syscall of error
| `Invalid_date
| `Invalid_test
| `No_process
| `Not_found ] )
result,
s )
io =
fun ({ bind; return } as scheduler) syscall fd abs_offset metadata operation ->
match operation with
| Tree.Name _ -> return (Error `No_process)
| Tree.Use _ -> return (Error `No_process)
| Tree.MIME v -> return (Ok (abs_offset, Metadata.with_mime v metadata))
| Tree.Extension vs ->
return (Ok (abs_offset, Metadata.with_extensions vs metadata))
| Tree.Rule (offset, ty, test, fmt) -> (
let ( >>= ) = bind in
let ( >|= ) x f = x >>= fun x -> return (f x) in
let ( >?= ) x f =
x >>= function Ok x -> f x | Error err -> return (Error err)
in
Offset.process scheduler syscall fd offset abs_offset
>|= reword_error (fun err -> `Syscall err)
>?= fun abs_offset ->
Ty.process scheduler syscall fd abs_offset ty >?= fun v ->
match Test.process ty test v with
| Some v ->
let metadata = process_fmt metadata ty fmt v in
return (Ok (abs_offset, metadata))
| None -> return (Error `Invalid_test))
let descending_walk ({ bind; return } as scheduler) syscall db fd abs_offset
metadata root =
let ( >>= ) = bind in
let rec go ~level syscall abs_offset candidate0 = function
| Tree.Done -> return candidate0
| Tree.Node lst ->
let lst =
List.rev_map (fun (elt, sub) -> (Tree.operation elt, sub)) lst
in
iter ~level [] syscall abs_offset candidate0 lst
and iter ~level results syscall abs_offset candidate1 = function
| [] -> return candidate1
| (Tree.Name _, _) :: rest ->
iter ~level results syscall abs_offset candidate1 rest
| (Tree.Use { offset; invert = false; name }, Tree.Done) :: rest -> (
Offset.process scheduler syscall fd offset abs_offset >>= function
| Ok shift ->
let seek fd abs_offset where =
syscall.seek fd (Int64.add abs_offset shift) where
in
if not (Hashtbl.mem db name) then
invalid_arg "%s does not exist" name;
let tree = Hashtbl.find db name in
go { syscall with seek } ~level:(succ level) 0L
candidate1 tree
>>= fun candidate2 ->
iter ~level results syscall abs_offset candidate2 rest
| Error _ -> iter ~level results syscall abs_offset candidate1 rest)
| (Tree.Use { offset; invert = true; name }, Tree.Done) :: rest -> (
Offset.process scheduler syscall fd offset abs_offset >>= function
| Ok shift ->
let seek fd abs_offset where =
syscall.seek fd (Int64.add abs_offset shift) where
in
if not (Hashtbl.mem db name) then
invalid_arg "%s does not exist" name;
let tree = Hashtbl.find db name in
go
(Size.invert scheduler { syscall with seek })
~level:(succ level) 0L
candidate1 tree
>>= fun candidate2 ->
iter ~level results syscall abs_offset candidate2 rest
| Error _ -> iter ~level results syscall abs_offset candidate1 rest)
| (Tree.Rule (offset, Ty.Indirect `Rel, _, _), Tree.Done) :: rest -> (
Offset.process scheduler syscall fd offset abs_offset >>= function
| Ok shift ->
let seek fd abs_offset where =
syscall.seek fd (Int64.add abs_offset shift) where
in
let metadata = Metadata.empty in
go { syscall with seek } ~level:(succ level) abs_offset metadata
root
>>= fun metadata ->
let candidate1 = Metadata.concat candidate1 metadata in
iter ~level (candidate1 :: results) syscall
(Int64.add abs_offset shift)
candidate1 rest
| Error _ -> iter ~level results syscall abs_offset candidate1 rest)
| ((Tree.Rule (_, Ty.Default, _, _) as operation), tree) :: rest -> (
match results with
| _ :: _ -> iter ~level results syscall abs_offset candidate1 rest
| [] -> (
process scheduler syscall fd abs_offset candidate1 operation
>>= function
| Ok (abs_offset, candidate2) ->
go syscall ~level:(succ level) abs_offset candidate2 tree
>>= fun candidate3 ->
iter ~level (candidate3 :: results) syscall abs_offset
candidate3 rest
| Error _ -> iter ~level [] syscall abs_offset candidate1 rest))
| (operation, tree) :: rest -> (
process scheduler syscall fd abs_offset candidate1 operation
>>= function
| Ok (abs_offset, candidate1) ->
go syscall ~level:(succ level) abs_offset candidate1 tree
>>= fun candidate2 ->
iter ~level (candidate2 :: results) syscall abs_offset candidate2
rest
| Error _ -> iter ~level results syscall abs_offset candidate1 rest)
in
go ~level:0 syscall abs_offset metadata root
type database = (string, Tree.t) Hashtbl.t * Tree.t
let rec fill_db db = function
| Tree.Done -> ()
| Tree.Node lst ->
let rec go = function
| [] -> ()
| (Tree.Name (_, name), tree) :: rest ->
Hashtbl.add db name tree;
fill_db db tree;
go rest
| (_, tree) :: rest ->
fill_db db tree;
go rest
in
go (List.rev_map (fun (elt, sub) -> (Tree.operation elt, sub)) lst)
let database ~tree : database =
let db = Hashtbl.create 0x10 in
fill_db db tree;
(db, tree)
let append ~tree (db, tree') : database =
fill_db db tree;
(db, Tree.merge tree tree')
let descending_walk scheduler syscall fd (db, tree) =
descending_walk scheduler syscall db fd 0L Metadata.empty tree
let has_mime_tag (db, tree) =
let visited = Hashtbl.create 0x10 in
let rec iter has_mime_tag = function
| [] -> ()
| (Tree.Name _, tree) :: rest ->
go has_mime_tag tree;
if not !has_mime_tag then iter has_mime_tag rest
| (Tree.Use { name; _ }, tree) :: rest ->
(if not (Hashtbl.mem visited name) then
match Hashtbl.find_opt db name with
| Some tree' ->
Hashtbl.add visited name ();
go has_mime_tag tree'
| None -> ());
if not !has_mime_tag then go has_mime_tag tree;
if not !has_mime_tag then iter has_mime_tag rest
| (Tree.MIME _, _) :: _rest -> has_mime_tag := true
| (Tree.Extension _, _) :: _rest -> ()
| (Tree.Rule _, tree) :: rest ->
go has_mime_tag tree;
if not !has_mime_tag then iter has_mime_tag rest
and go has_mime_tag = function
| Tree.Done -> ()
| Tree.Node lst ->
let lst =
List.rev_map (fun (elt, sub) -> (Tree.operation elt, sub)) lst
in
(iter [@tailcall]) has_mime_tag lst
in
let has_mime_tag = ref false in
go has_mime_tag tree;
!has_mime_tag
let only_mime_paths (db, tree) =
let rec go = function
| Tree.Done -> Tree.Unsafe.leaf
| Tree.Node lst ->
let lst =
List.rev_map (fun (elt, sub) -> (Tree.operation elt, sub)) lst
in
let f acc (operation, sub) =
let sub = go sub in
let sub_has_mime_tag = has_mime_tag (db, sub) in
match operation with
| Tree.MIME _ -> (Tree.Unsafe.elt operation, sub) :: acc
| _ when sub_has_mime_tag -> (Tree.Unsafe.elt operation, sub) :: acc
| _ -> acc
in
Tree.Unsafe.node (List.fold_left f [] lst)
in
go tree
let mimes_and_extensions ~f acc (db, tree) =
let visited = Hashtbl.create 0x100 in
let rec go (mime, extension) acc tree =
match (mime, extension, tree) with
| Some mime, (_ :: _ as exts), Tree.Done -> f ~mime ~exts acc
| None, _, Tree.Done | Some _, [], Tree.Done -> acc
| _, _, Tree.Node elts -> (
let fold (mime, exts, acc) (elt, tree) =
match Tree.operation elt with
| Tree.MIME mime ->
(Some mime, exts, go (Some mime, extension) acc tree)
| Tree.Extension exts' ->
let exts = List.merge String.compare exts exts' in
(mime, exts, go (mime, exts) acc tree)
| Tree.Use { name; _ } ->
if not (Hashtbl.mem visited name) then
match Hashtbl.find_opt db name with
| Some tree' ->
Hashtbl.add visited name ();
let acc = go (mime, exts) acc tree' in
(mime, exts, go (mime, exts) acc tree)
| None -> (mime, exts, go (mime, exts) acc tree)
else (mime, exts, acc)
| _ -> (mime, exts, go (mime, extension) acc tree)
in
let mime, exts, acc = List.fold_left fold (mime, extension, acc) elts in
match (mime, exts) with
| Some mime, (_ :: _ as exts) -> f ~mime ~exts acc
| _ -> acc)
in
go (None, []) acc tree
let rec ascending_walk ({ bind; return } as scheduler) syscall db fd results
queue =
let ( >>= ) = bind in
match Queue.pop queue with
| _, candidate, Tree.Done ->
ascending_walk scheduler syscall db fd (candidate :: results) queue
| abs_offset, candidate, Tree.Node lst ->
let lst =
List.rev_map (fun (elt, sub) -> (Tree.operation elt, sub)) lst
in
let rec go candidate = function
| [] -> return ()
| (Tree.Name (_, name), tree) :: rest ->
Hashtbl.add db name tree;
go candidate rest
| (Tree.Use { name; _ }, Tree.Done) :: rest ->
let tree = Hashtbl.find db name in
Queue.push (abs_offset, candidate, tree) queue;
go candidate rest
| (operation, tree) :: rest -> (
process scheduler syscall fd abs_offset candidate operation
>>= function
| Ok (abs_offset, candidate) ->
Queue.push (abs_offset, candidate, tree) queue;
go candidate rest
| Error _ -> go candidate rest)
in
go candidate lst >>= fun () ->
ascending_walk scheduler syscall db fd results queue
| exception Queue.Empty -> return (List.rev results)
let ascending_walk scheduler syscall fd tree =
let queue = Queue.create () in
let db = Hashtbl.create 0x10 in
Queue.push (0L, Metadata.empty, tree) queue;
ascending_walk scheduler syscall db fd [] queue