Source file b0_file.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 B0_std
open B0_std.Result.Syntax
open B0_text
type smeta = Tloc.t
let smeta ~loc = loc
let loc m = m
let pp_loc = Tloc.pp_ocaml
let loc_err_fmt ffmt m fmt =
ffmt ("@[<v>%a:@,@[%a: " ^^ fmt ^^ "@]@]")
pp_loc (loc m) (Fmt.st [`Fg `Red; `Bold ]) "Error"
let loc_errf m fmt = loc_err_fmt Fmt.str m fmt
let loc_error m fmt = loc_err_fmt Fmt.error m fmt
type b0_boot = (string * smeta) list
type b0_include = (string * smeta) * (Fpath.t * smeta)
type require = B0_ocaml.Libname.t * smeta
type mod_use = Fpath.t * smeta
type t =
{ file : Fpath.t;
cwd : Fpath.t;
b0_boots : b0_boot list;
b0_includes : b0_include list;
requires : require list;
mod_uses : mod_use list;
ocaml_unit : string * smeta; }
let file f = f.file
let cwd f = f.cwd
let b0_includes f = f.b0_includes
let b0_boots f = f.b0_boots
let requires f = f.requires
let mod_uses f = f.mod_uses
let ocaml_unit f = f.ocaml_unit
let pp_dump ppf s =
let pp_fst pp = Fmt.using fst pp in
let pp_strings = Fmt.(list ~sep:sp (pp_fst Fmt.string)) in
let pp_boots = Fmt.vbox @@ Fmt.list (Fmt.box pp_strings) in
let pp_includes =
let pp_include ppf ((n, _), (p, _)) =
Fmt.pf ppf "@[%s %a@]" n Fpath.pp_quoted p
in
Fmt.(vbox @@ list pp_include)
in
let pp_reqs = Fmt.(list ~sep:sp (pp_fst B0_ocaml.Libname.pp)) in
let pp_mod_uses = Fmt.(list (pp_fst Fpath.pp_unquoted)) in
Fmt.record
[ Fmt.field "file" file Fpath.pp_quoted;
Fmt.field "b0-boots" b0_boots pp_boots;
Fmt.field "b0-includes" b0_includes pp_includes;
Fmt.field "requires" requires pp_reqs;
Fmt.field "mod_uses" mod_uses pp_mod_uses;
Fmt.field "ocaml_unit" ocaml_unit (Fmt.box @@ pp_fst Fmt.lines)]
ppf s
let pp_locs ppf s =
let pp_loc ppf (_, smeta) = Fmt.pf ppf "%a:" pp_loc (loc smeta) in
let pp_loc_pair ppf (a, b) = pp_loc ppf a; Fmt.cut ppf (); pp_loc ppf b in
let pp_list pp_v ppf = function
| [] -> () | l -> Fmt.list pp_v ppf l; Fmt.cut ppf ()
in
Fmt.pf ppf "@[<v>%a%a%a%a%a@]"
(pp_list (pp_list pp_loc)) (b0_boots s)
(pp_list pp_loc_pair) (b0_includes s)
(pp_list pp_loc) (requires s)
(pp_list pp_loc) (mod_uses s)
pp_loc (ocaml_unit s)
let directives = ["@@@B0.boot"; "@@@B0.include"; "#requires"; "#mod_use"]
let pp_directive = Fmt.code
let is_dir_letter c =
(0x61 <= c && c <= 0x7A) || c = 0x5F || (0x41 <= c && c <= 0x5A) ||
c = 0x2E || c = 0x40 || (0x30 <= c && c <= 0x39)
let err_eoi msg d ~sbyte ~sline =
Tdec.err_to_here d ~sbyte ~sline "Unexpected end of input: %s" msg
let err_eoi_string = err_eoi "unclosed string"
let err_eoi_esc = err_eoi "truncated escape"
let err_eoi_dir = err_eoi "unclosed directive"
let err_exp_dir_arg d = Tdec.err_here d "Expected directive argument"
let err_illegal_uchar d b = Tdec.err_here d "Illegal character U+%04X" b
let err_exp_eodir d ~sbyte ~sline =
Tdec.err_to_here d ~sbyte ~sline "Expected end of directive (']')"
let curr_char d =
Tdec.tok_reset d; Tdec.tok_accept_uchar d; Tdec.tok_pop d
let err_esc_illegal d ~sbyte ~sline pre =
Tdec.err_to_here d ~sbyte ~sline "%s%s: illegal escape" pre (curr_char d)
let err_unsupported_directive sharp dir_loc dir =
let hint = Fmt.must_be in
let unknown = Fmt.(unknown' ~kind:(any "directive") pp_directive ~hint) in
Tdec.err dir_loc (Fmt.str "@[%a@]" unknown (sharp ^ dir, directives))
let dec_byte d = match Tdec.byte d with
| c when 0x00 <= c && c <= 0x08 || 0x0E <= c && c <= 0x1F || c = 0x7F ->
err_illegal_uchar d c
| c -> c
[@@ ocaml.inline]
let rec skip_ws d = match dec_byte d with
| 0x20 | 0x09 | 0x0A | 0x0B | 0x0C | 0x0D -> Tdec.accept_byte d; skip_ws d
| _ -> ()
let parse_esc d =
let sbyte = Tdec.pos d and sline = Tdec.line d in
match (Tdec.accept_byte d; dec_byte d) with
| 0x22 -> Tdec.accept_byte d; Tdec.tok_add_char d '"'
| 0x5C -> Tdec.accept_byte d; Tdec.tok_add_char d '\\'
| 0x0A | 0x0D -> skip_ws d
| 0xFFFF -> err_eoi_esc d ~sbyte ~sline
| _ -> err_esc_illegal d ~sbyte ~sline "\\"
let parse_string d = match (skip_ws d; dec_byte d) with
| 0x22 ->
let rec loop d ~sbyte ~sline = match dec_byte d with
| 0x22 ->
let loc = Tdec.loc_to_here d ~sbyte ~sline in
let arg = Tdec.tok_pop d in
Tdec.accept_byte d; (arg, smeta ~loc)
| 0x5C -> parse_esc d; loop d ~sbyte ~sline
| 0xFFFF -> err_eoi_string d ~sbyte ~sline
| _ -> Tdec.tok_accept_byte d; loop d ~sbyte ~sline
in
let sbyte = Tdec.pos d and sline = Tdec.line d in
Tdec.accept_byte d; loop ~sbyte ~sline d
| c -> err_exp_dir_arg d
let string_to parse (arg, smeta) =
match parse arg with Ok v -> v, smeta | Error e -> Tdec.err (loc smeta) e
let parse_fpath d = string_to Fpath.of_string (parse_string d)
let rec parse_directive_name d ~sbyte ~sline = match dec_byte d with
| c when is_dir_letter c ->
Tdec.tok_accept_byte d; parse_directive_name d ~sbyte ~sline
| c ->
let ebyte = Tdec.pos d - 1 and eline = Tdec.line d in
let loc = Tdec.loc d ~sbyte ~ebyte ~sline ~eline in
Tdec.tok_pop d, loc
let rec parse_annot_arg_list d acc p_arg ~sbyte ~sline =
match (skip_ws d; dec_byte d) with
| 0x5D -> Tdec.accept_byte d; List.rev acc
| 0xFFFF -> err_eoi_dir d ~sbyte ~sline
| _ -> parse_annot_arg_list d (p_arg d :: acc) p_arg ~sbyte ~sline
let parse_boot_directive d ~sbyte ~sline =
parse_annot_arg_list d [] parse_string ~sbyte ~sline
let parse_include_directive d ~sbyte ~sline =
let parse_scope_name (s, smeta as scope) =
if s = "" then Tdec.err (loc smeta) "Scope name cannot be empty." else
if not (String.exists (Char.equal '.') s) then scope else
Tdec.err (loc smeta)
(Fmt.str "Scope name %a contains a dot." Fmt.code s)
in
let arg = parse_string d in
match skip_ws d; dec_byte d with
| 0x5D ->
Tdec.accept_byte d;
let (p, smeta as file) = string_to Fpath.of_string arg in
let scope = Fpath.(basename @@ parent p), smeta in
parse_scope_name scope, file
| 0xFFFF -> err_eoi_dir d ~sbyte ~sline
| _ ->
let file = string_to Fpath.of_string (parse_string d) in
match skip_ws d; dec_byte d with
| 0x5D -> Tdec.accept_byte d; (parse_scope_name arg, file)
| 0xFFFF -> err_eoi_dir d ~sbyte ~sline
| _ -> err_exp_eodir d ~sbyte ~sline
let parse_require_directive d ~sbyte ~sline =
string_to B0_ocaml.Libname.of_string (parse_string d)
let parse_mod_use_directive d ~sbyte ~sline =
string_to Fpath.of_string (parse_string d)
let parse_preamble d =
let rec loop boots incs reqs mus d = match skip_ws d; dec_byte d with
| 0x23 ->
let sbyte = Tdec.pos d and sline = Tdec.line d in
Tdec.accept_byte d;
begin match parse_directive_name d ~sbyte ~sline with
| "require", _ ->
let r = parse_require_directive d ~sbyte ~sline in
loop boots incs (r :: reqs) mus d
| "mod_use", _ ->
let m = parse_mod_use_directive d ~sbyte ~sline in
loop boots incs reqs (m :: mus) d
| dir, dir_loc ->
err_unsupported_directive "#" dir_loc dir
end
| 0x5B ->
let sbyte = Tdec.pos d and sline = Tdec.line d in
Tdec.accept_byte d;
begin match parse_directive_name d ~sbyte ~sline with
| "@@@B0.boot", _ ->
let b = parse_boot_directive d ~sbyte ~sline in
loop (b :: boots) incs reqs mus d
| "@@@B0.include", _ ->
let i = parse_include_directive d ~sbyte ~sline in
loop boots (i :: incs) reqs mus d
| dir, dir_loc ->
err_unsupported_directive "" dir_loc dir
end
| _ ->
List.rev boots, List.rev incs, List.rev reqs, List.rev mus
in
loop [] [] [] [] d
let of_string ~file src =
try
let d = Tdec.create ~file:(Fpath.to_string file) src in
let b0_boots, b0_includes, requires, mod_uses = parse_preamble d in
let rest = String.subrange ~first:(Tdec.pos d) src in
let ocaml_unit = rest, smeta ~loc:(Tdec.loc_here d) in
let cwd = Fpath.parent file in
Ok { file; cwd; b0_boots; b0_includes; requires; ocaml_unit; mod_uses }
with Tdec.Err (loc, e) -> loc_error loc "%a" (Fmt.vbox Fmt.lines) e
type expanded =
{ expanded_file_manifest : Fpath.t list;
expanded_b0_boots : b0_boot list;
expanded_b0_includes : b0_include list;
expanded_requires : require list;
expanded_src : string; }
let expanded_file_manifest e = e.expanded_file_manifest
let expanded_b0_boots e = e.expanded_b0_boots
let expanded_b0_includes e = e.expanded_b0_includes
let expanded_requires e = e.expanded_requires
let expanded_src e = e.expanded_src
let get_include_src b0_file (p, smeta) =
let src =
let* file = Os.Path.realpath Fpath.((cwd b0_file) // p) in
let* src = Os.File.read file in
Ok (file, src)
in
match src with
| Ok (file, src) -> of_string ~file src |> Result.error_to_failure
| Error e ->
loc_err_fmt Fmt.failwith_notrace smeta "%s" e
let get_mod_use_srcs b0_file manif (p, smeta) =
let file_impl = Fpath.((cwd b0_file) // p) in
let file_intf = Fpath.(file_impl -+ ".mli") in
let res =
let* file_impl = Os.Path.realpath file_impl in
let* src_impl = Os.File.read file_impl in
let manif = file_impl :: manif in
let* exists = Os.File.exists file_intf in
if not exists then Ok (manif, None, (file_impl, src_impl)) else
let* file_intf = Os.Path.realpath file_intf in
let* src_intf = Os.File.read file_intf in
Ok (file_intf :: manif, Some (file_intf, src_intf), (file_impl, src_impl))
in
match res with
| Ok (files, intf, impl) -> files, intf, impl
| Error e -> loc_err_fmt Fmt.failwith_notrace smeta "%s" e
let nil_loc = "#1 \"-\""
let lineno meta =
let l = loc meta in
Fmt.str "#%d %S" (fst (Tloc.sline l)) (Tloc.file l)
let w acc l = l :: acc [@@ocaml.inline]
let w_src b (file, src) =
let b = w b (Fmt.str "#1 %S" (Fpath.to_string file)) in
let b = w b src in b
let w_mod_use b b0_file manif (p, _ as mod_use) =
let manif, intf, impl = get_mod_use_srcs b0_file manif mod_use in
let mod_name = B0_ocaml.Modname.mangle_filename (Fpath.basename p) in
let b = match intf with
| None -> w b (Fmt.str "module %s = struct" mod_name)
| Some intf ->
let b = w b (Fmt.str "module %s : sig" mod_name) in
let b = w_src b intf in
let b = w b "end = struct" in
b
in
let b = w_src b impl in
let b = w b "end" in
let b = w b nil_loc in
b, manif
let rec w_mod_uses b b0_file manif =
let rec loop b file files = function
| [] -> b, files
| mod_use :: mod_uses ->
let b, manif = w_mod_use b file manif mod_use in
loop b file manif mod_uses
in
loop b b0_file manif (mod_uses b0_file)
let w_ocaml_unit b (src, src_meta) =
let b = w b (lineno src_meta) in
let b = w b src in b
let rec w_include b b0_file id npre manif boots incs reqs ((n, nm), inc_file) =
let b0_file = get_include_src b0_file inc_file in
let b = w b nil_loc in
let b = w b (Fmt.str "module Inc_%03d : sig end = struct" id) in
let b =
let f = Fpath.to_string (file b0_file) in
w b (Fmt.str "let () = B0_scope.open_file %S (B0_std.Fpath.v %S)" n f)
in
let b, id, manif, boots, incs, reqs =
let id = id + 1 in
let file = file b0_file in
let npre = if npre = "" then n else String.concat "." [npre; n] in
let incs = ((npre, nm), (file, snd inc_file)) :: incs in
w_includes b b0_file id npre manif boots incs reqs
in
let b, manif = w_mod_uses b b0_file manif in
let b = w_ocaml_unit b (ocaml_unit b0_file) in
let b = w b nil_loc in
let b = w b "let () = B0_scope.close ()" in
let b = w b "end" in
b, id, manif, boots, incs, reqs
and w_includes b b0_file id npre manif boots incs reqs =
let check_scope_unique (n, smeta) nmap = match String.Map.find n nmap with
| exception Not_found -> String.Map.add n smeta nmap
| smeta' ->
loc_err_fmt Fmt.failwith
smeta "@[<v>Scope name %a already defined.@,\
Previous definition:@,%a:@]" Fmt.code n pp_loc smeta'
in
let rec loop b0_file id nmap npre b files boots incs reqs = function
| (name, _ as i) :: todo ->
let nmap = check_scope_unique name nmap in
let b, id, files, boots, incs, reqs =
w_include b b0_file id npre files boots incs reqs i
in
loop b0_file id nmap npre b files boots incs reqs todo
| [] ->
b, id, files, boots, incs, reqs
in
let manif = file b0_file :: manif in
let boots = List.rev_append (b0_boots b0_file) boots in
let reqs = List.rev_append (requires b0_file) reqs in
loop b0_file id String.Map.empty npre b manif boots incs reqs
(b0_includes b0_file)
let expand b0_file =
try
let b = w [] nil_loc in
let r = Fpath.to_string (file b0_file) in
let b = w b
(Fmt.str "let () = B0_scope.open_root (B0_std.Fpath.v %S)" r)
in
let b, _, manif, boots, incs, reqs = w_includes b b0_file 0 "" [] [] [][] in
let b, manif = w_mod_uses b b0_file manif in
let b = w_ocaml_unit b (ocaml_unit b0_file) in
let b = w b nil_loc in
let b = w b "let () = B0_scope.close ()" in
let b = w b "let () = B0_scope.seal ()" in
let b = w b "let () = B0_driver.run ~has_b0_file:true" in
Ok { expanded_file_manifest = List.rev manif;
expanded_b0_boots = List.rev boots;
expanded_b0_includes = List.rev incs;
expanded_requires = List.rev reqs;
expanded_src = String.concat "\n" (List.rev b) }
with Failure e -> Error e