Source file schematic.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
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
type format = Json | Sexp
type (_,_) eq = Eq : ('a,'a) eq
type void = (int,float) eq
module L = struct
type 'a t = 'a list =
| [ ]
| (::) of 'a * 'a t
end
module Tuple = struct
type 'a tuple =
| []: void tuple
| (::): 'a * 'b tuple -> ('a * 'b) tuple
type 'a t = 'a tuple
end
open Tuple
module type label = sig type t val l:string end
type 'a label = (module label with type t = 'a)
module Label(X:sig val l:string end) =
struct
type t
type s = t
include X
module M = struct type t = s let l = l end
let l: t label = (module M)
end
let show (type a) ((module X):a label) = X.l
type required = private Required
type optional = private Optional
type (_,_,_) modal =
| Opt:(optional,'a,'a option) modal
| Req:(required,'a,'a) modal
module Record = struct
type 'a record =
| []: void record
| (::): ( 'a label * 'elt) * 'c record ->
('a * 'elt * 'c) record
type 'a t = 'a record
end
open Record
type ('a,'b) bijection = { fwd:'a->'b;rev:'b -> 'a}
type 'a r = Indexed
type ('hole, 'free) s =
| Float: (float, 'free) s
| Int: (int, 'free) s
| Bool: (bool, 'free) s
| String: (string, 'free) s
| Void: (void, 'free) s
| Array: ('hole,'free) s -> ('hole list, 'free) s
| (::) : ('a,'free) s * ('b tuple, 'free) s -> (('a * 'b) tuple, 'free) s
| []: (void tuple, 'free) s
| Obj: ('a,'free) record_declaration -> ('a record, 'free) s
| Custom: ('a,'b,'free) custom -> ('a, 'free) s
| Sum: ('a,'free) sum_decl -> ('a sum,'free) s
| Description: string * ('hole,'free) s -> ('hole, 'free) s
| Rec: { id: string list; defs:('defs,'defs r) rec_defs; proj: ('defs, 'res) index}
-> ('res,'free) s
| Var: ('free, 'result) index -> ('result,'free r) s
and (_,_) rec_defs =
| []: (void,'free r) rec_defs
| (::): (string * ('a,'free r) s) * ('l, 'free r) rec_defs -> ('a * 'l, 'free r) rec_defs
and ('a,'b,'free) custom = { fwd:'a -> 'b; rev:'b -> 'a; sch:('b,'free) s }
and ('a,'free) record_declaration =
| []: (void, 'free) record_declaration
| (::): ( ('m,'x,'fx) modal * 'a label * ('x,'free) s) * ('c,'free) record_declaration
-> ( 'a * 'fx * 'c, 'free) record_declaration
and ('a,'mu) sum_decl =
| [] : (<before:void>, 'mu) sum_decl
| (::): (string * ('a,'mu) s) * ('b,'mu) sum_decl
-> (<at:'a; before:'b>,'mu) sum_decl
and (_,_) cons =
| Z: 'a -> (<at:'a; before: 'any>,'a) cons
| E: (<at:void; before:'any>,'a) cons
| S: ('a, 'n ) cons -> (<at:'any; before:'a>, 'n) cons
and 'a sum = C: ('a, 'elt ) cons -> 'a sum
and (_,_) index =
| Zn: ( 'a * 'b , 'a) index
| Sn: ('list, 'res) index -> ( _ * 'list, 'res) index
type 'a t = ('a,void) s
type 'a schematic = 'a t
let rec get: type a all res. (a,all r) rec_defs -> (a, res) index -> (res,all r) s = fun l x ->
match l, x with
| (_,a)::_ , Zn -> a
| _ :: q, Sn x -> get q x
| [], _ -> .
let rec reopen: type a b. a t -> (a,b) s =
function
| Float -> Float
| Int -> Int
| Void -> Void
| Bool -> Bool
| String -> String
| Rec {id;proj;defs} -> Rec {id;proj;defs}
| [] -> []
| x :: q -> reopen x :: reopen q
| Array x -> Array (reopen x)
| Obj q -> Obj (reopen_obj q)
| Custom {rev;fwd;sch} -> Custom {rev;fwd;sch=reopen sch}
| Sum x -> Sum (reopen_sum x)
| Description(d,x) -> Description(d, reopen x)
| _ -> .
and reopen_obj: type a f. (a,void) record_declaration -> (a,f) record_declaration = function
| [] -> []
| (Req, lbl, a) :: q -> (Req, lbl, reopen a) :: reopen_obj q
| (Opt, lbl, a) :: q -> (Opt, lbl, reopen a) :: reopen_obj q
and reopen_sum: type a f. (a,void) sum_decl -> (a,f) sum_decl = function
| (name, a) :: q -> (name, reopen a) :: reopen_sum q
| [] -> []
let custom sch fwd rev = Custom {fwd;rev; sch}
module Version = struct
type t = { major:int; minor:int; patch:int }
module Lbl = Label(struct let l = "version" end)
type lbl = Lbl.t
let sch =
custom [Int;Int;Int]
(fun x -> [x.major;x.minor;x.patch])
(fun [major;minor;patch] -> {major;minor;patch})
end
let ($=) field x = field, x
let k ppf name = Pp.fp ppf {|"%s"|} name
let p ppf (key,data)=
Pp.fp ppf {|@[%a@ :@ "%s"@]|} k key data
let ty ppf data = p ppf ("type",data)
type 'a tree =
| Item of string list * 'a
| M of 'a forest
and 'a forest = 'a tree Name.map
let arbitrary_name ctx = "id" ^ string_of_int ctx, ctx + 1
let find_nearly name map =
let find k arg name =
match Name.Map.find name map with
| exception Not_found -> name, Name.Map.empty
| Item _ -> k arg
| M m -> name, m in
let rec loop n =
let name = name ^ string_of_int n in
find loop (n+1) name in
find loop 2 name
module Path_map=
Map.Make(struct
type t= string list
let compare (x:string list) y = compare x y
end)
type 'a pending_rec_def =
| Pending: { id: string list; defs: ('a,'a r) rec_defs } -> 'a r pending_rec_def
| Closed: void pending_rec_def
type dyn = Dyn: 'f pending_rec_def * ('a,'f) s -> dyn
type effective_paths = (string list * int) list Path_map.t
type context = { stamp:int; mapped: effective_paths }
let id (x: (_,_) s) = Hashtbl.hash x
type def = { desc: string list; ctx: context; map: dyn forest }
let add_path (type a f) path (defs:f pending_rec_def) (x:(a,f) s) {desc;ctx;map} =
let open L in
let rec add_path ctx path x map =
match path with
| [] -> let name, ctx = arbitrary_name ctx in add_path ctx [name] x map
| [name] ->
let name, m = find_nearly name map in
if m = Name.Map.empty then
ctx, [name], Name.Map.add name (Item (desc,Dyn (defs,x))) map
else
let ctx, q, m = add_path ctx [] x m in
ctx, name :: q, Name.Map.add name (M m) map
| a :: q->
let a, m = find_nearly a map in
let ctx, q, v = add_path ctx q x m in
ctx, a :: q, Name.Map.add a (M v) map in
match Path_map.find path ctx.mapped with
| exception Not_found ->
let stamp, effective_path, map = add_path ctx.stamp path x map in
let mapped = Path_map.add path [effective_path, id x] ctx.mapped in
{ desc = L.[]; ctx = {stamp;mapped}; map }
| l when List.exists (fun (_,d) -> (d = id x)) l ->
{ desc = L.[]; ctx; map}
| l ->
let stamp, effective_path, map = add_path ctx.stamp path x map in
let mapped =
Path_map.add path ( (effective_path, id x) :: l ) ctx.mapped in
{ desc = L.[]; ctx = { stamp; mapped}; map }
let mem (path,x) ctx = match Path_map.find path ctx.mapped with
| exception Not_found -> false
| l -> List.exists (fun (_,h) -> h = id x) l
let find_path (path,(x:(_,_) s)) mapped = try
String.concat "/" @@ fst
@@ List.find (fun (_,idy) -> id x = idy) @@ Path_map.find path mapped
with
| Not_found ->
Format.eprintf "Unbound schema definition: %a@." Pp.(list ~sep:(const "/") string) path; exit 2
let rec to_int: type l x. (l,x) index -> int = function
| Zn -> 0
| Sn x -> 1 + to_int x
let defs s =
let rec :
type a f. defs:f pending_rec_def -> (a,f) s -> def -> def =
fun ~defs sch data ->
let traverse x = extract_def ~defs x in
match sch with
| Float -> data | Int -> data | String -> data | Bool -> data | Void -> data
| Array t -> data |> traverse t
| Obj [] -> data
| Obj ( (_,_,x) :: q ) ->
data |> traverse x |> traverse (Obj q)
| [] -> data
| a :: q -> data |> traverse a |> traverse q
| Sum x -> extract_sum_def ~defs x data
| Custom{sch; _ } -> traverse sch data
| Description (_,x) -> traverse x data
| Rec { id; proj; defs=defs' } ->
let n = string_of_int @@ to_int proj in
let p = L.( id @ [n] ) in
let sch = get defs' proj in
if mem (p, sch) data.ctx then data else
data |> add_path p (Pending {id;defs=defs'}) sch |> extract_def ~defs:(Pending {id;defs=defs'}) sch
| Var n ->
let Pending defs as p = defs in
let path = L.( defs.id @ [string_of_int (to_int n)]) in
let sch = get defs.defs n in
if mem (path, sch) data.ctx then data else
data |> add_path path (Pending defs) sch |> extract_def ~defs:p sch
and : type a b. defs:b pending_rec_def ->(a,b) sum_decl -> def -> def =
fun ~defs s data ->
match s with
| [] -> data
| (_,t) :: q -> data |> extract_def ~defs t |> extract_sum_def ~defs q
in extract_def ~defs s
{ desc = L.[];
ctx = {stamp=1;mapped=Path_map.empty};
map = Name.Map.empty
}
let pp_descr ppf l =
if l = L.[] then ()
else
Pp.fp ppf {|"description":%S,@ |}
(String.concat " " @@ List.rev l)
let tyd ppf (dl,typ) = Pp.fp ppf "%a%a" pp_descr dl ty typ
let rec json_type: type a f. effective_paths -> recs: f pending_rec_def
-> string list -> Format.formatter -> (a,f) s -> unit =
fun epaths ~recs descr ppf -> function
| Float -> tyd ppf (descr,"number")
| Int -> tyd ppf (descr,"number")
| String -> tyd ppf (descr,"string")
| Bool -> tyd ppf (descr,"string")
| Void -> ()
| Array t -> Pp.fp ppf
"%a,@;@[<hov 2>%a : {@ %a@ }@]"
tyd (descr,"array") k "items" (json_type ~recs epaths L.[]) t
| [] -> ()
| _ :: _ as l ->
Pp.fp ppf "%a,@; @[<hov 2>%a :[@ %a@ ]@]" tyd (descr,"array") k "items"
(json_schema_tuple epaths ~recs ) l
| Obj r ->
Pp.fp ppf "%a,@;@[<v 2>%a : {@ %a@ }@],@;@[<hov 2>%a@ :@ [@ %a@ ]@]"
tyd (descr,"object")
k "properties"
(json_properties ~recs epaths) r
k "required"
(json_required true) r
| Custom { sch; _ } -> json_type ~recs epaths descr ppf sch
| Sum decl ->
Pp.fp ppf "@[<hov 2>%a%a :[%a]@]"
pp_descr descr
k "oneOf" (json_sum ~recs epaths true 0) decl
| Description (d, sch) -> json_type ~recs epaths L.(d::descr) ppf sch
| Rec {proj; defs; id } -> json_type ~recs:(Pending { id; defs }) epaths descr ppf (get defs proj)
| Var n ->
let Pending recs = recs in
let path = L. (recs.id @ [string_of_int (to_int n)] ) in
let epath = find_path (path, get recs.defs n) epaths in
Pp.fp ppf {|@["$ref":"#/definitions/%s"@]|} epath
and json_schema_tuple:
type a f. effective_paths -> recs:f pending_rec_def -> Format.formatter -> (a tuple,f) s -> unit =
fun epaths ~recs ppf -> function
| [] -> ()
| [a] -> Pp.fp ppf {|@[<hov 2>{@ %a@ }@]|} (json_type ~recs epaths L.[]) a
| a :: q ->
Pp.fp ppf {|@[<hov 2>{@ %a@ }@],@; %a|}
(json_type epaths ~recs L.[]) a (json_schema_tuple ~recs epaths) q
| Description(_, x) -> json_schema_tuple ~recs epaths ppf x
| Rec {proj; defs; id } -> json_schema_tuple ~recs:(Pending { id; defs }) epaths ppf (get defs proj)
| Var n ->
let Pending recs = recs in
let path = L. (recs.id @ [string_of_int (to_int n)] ) in
let epath = find_path (path, get recs.defs n) epaths in
Pp.fp ppf {|@["$ref":#/definitions/%s"@]|} epath
| Custom _ -> assert false
| _ -> .
and json_properties:
type a f. effective_paths -> recs:f pending_rec_def -> Format.formatter -> (a,f) record_declaration -> unit =
fun epath ~recs ppf -> function
| [] -> ()
| [_, n, a] -> Pp.fp ppf {|@[<hov 2>"%s" : {@ %a@ }@]|}
(show n) (json_type ~recs epath L.[]) a
| (_, n, a) :: q ->
Pp.fp ppf {|@[<hov 2>"%s" : {@ %a@ }@],@;%a|}
(show n) (json_type ~recs epath L.[]) a (json_properties ~recs epath) q
and json_required: type a f. bool ->Format.formatter -> (a,f) record_declaration
-> unit =
fun first ppf -> function
| [] -> ()
| (Req, n, _) :: q ->
Pp.fp ppf {|%t"%s"%a|}
(fun ppf -> if not first then Pp.fp ppf ",@ " else ())
(show n)
(json_required false) q
| _ :: q -> json_required first ppf q
and json_sum:
type a b. effective_paths -> recs: b pending_rec_def -> bool -> int -> Format.formatter -> (a,b) sum_decl -> unit =
fun epaths ~recs first n ppf -> function
| [] -> ()
| (s, []) :: q ->
if not first then Pp.fp ppf ",@,";
Pp.fp ppf "@[{%a@ :@ [\"%s\"]}@]%a" k "enum" s
(json_sum ~recs epaths false @@ n + 1) q
| (s, Void) :: q ->
if not first then Pp.fp ppf ",@,";
Pp.fp ppf "@[{%a@ :@ [\"%s\"]}@]%a" k "enum" s
(json_sum ~recs epaths false @@ n + 1) q
| (s,a)::q ->
if not first then Pp.fp ppf ",@,";
let module N = Label(struct let l = s end) in
Pp.fp ppf "{%a}%a" (json_type ~recs epaths L.[]) (Obj[Req,N.l,a])
(json_sum epaths ~recs false @@ n + 1) q
let json_definitions epaths ppf map =
let rec json_def ppf name x not_first =
if not_first then Pp.fp ppf ",@,";
match x with
| Item (d, Dyn (ctx,x)) ->
Pp.fp ppf "@[%a%a@ :@ {@ %a@ }@]" pp_descr d
k name (json_type ~recs:ctx epaths L.[]) x;
true
| M m ->
Pp.fp ppf "@[%a@ :@ {@ %a@ }@ @]" k name json_defs m; true
and json_defs ppf m = ignore (Name.Map.fold (json_def ppf) m false) in
json_defs ppf map
module Pretty_json = struct
let rec json: type a f. f pending_rec_def -> (a,f) s -> Format.formatter -> a -> unit =
fun defs sch ppf x -> match sch, x with
| Int, n -> Pp.fp ppf "%d" n
| Float, f -> Pp.fp ppf "%f" f
| String, s -> Pp.estring ppf s
| Bool, b -> Pp.fp ppf "%b" b
| Void, _ -> .
| Array k, l ->
Pp.fp ppf "@[<hov>[%a]@]"
(Pp.list ~sep:(Pp.s ",@ ") @@ json defs k) l
| [], [] -> ()
| _ :: _ as sch , l -> Pp.fp ppf "@[<hov>[%a]@]" (json_tuple defs sch) l
| Obj sch, x -> Pp.fp ppf "@[<hv>{@ %a@ }@]" (json_obj false defs sch) x
| Custom c, x -> json defs c.sch ppf (c.fwd x)
| Sum q, x -> json_sum 0 defs q ppf x
| Description(_,sch), x -> json defs sch ppf x
| Var n, x ->
let Pending p = defs in
json defs (get p.defs n) ppf x
| Rec { defs; id; proj; _ }, x -> json (Pending {defs; id}) (get defs proj) ppf x
and json_sum: type all x. int -> all pending_rec_def -> (x,all) sum_decl ->
Format.formatter -> x sum -> unit =
fun n defs sch ppf x -> match sch, x with
| (n,a) :: _ , C Z x ->
let module N = Label(struct let l=n end) in
json defs (Obj [Req, N.l, a]) ppf (Record.[N.l, x])
| (n,_) :: _ , C E ->
json defs String ppf n
| _ :: q, C S c -> json_sum (n+1) defs q ppf (C c)
| [], _ -> .
and json_tuple: type a f. f pending_rec_def -> (a tuple,f) s -> Format.formatter -> a tuple -> unit =
fun defs sch ppf x -> match sch, x with
| [], [] -> ()
| [a], [x] -> json defs a ppf x
| a :: q, x :: xs -> Pp.fp ppf "%a,@ %a" (json defs a) x (json_tuple defs q) xs
| Custom _, _ -> assert false
| Description _, _ -> assert false
| Rec { defs; proj; id; _ }, x -> json_tuple (Pending {id;defs}) (get defs proj) ppf x
| Var proj, x ->
let Pending p = defs in
json_tuple defs (get p.defs proj) ppf x
and json_obj: type a r.
bool -> r pending_rec_def -> (a,r) record_declaration -> Format.formatter -> a record -> unit =
fun not_first defs sch ppf x -> match sch, x with
| [], [] -> ()
| (Req, name,sch) :: q , (_, x) :: xs ->
if not_first then Pp.fp ppf ",@ ";
Pp.fp ppf {|@[<hov 2>"%s"@ :@ %a@]|} (show name) (json defs sch) x;
Pp.fp ppf "%a" (json_obj true defs q) xs
| (Opt,name,sch) :: q, (_,Some x) :: xs ->
if not_first then Pp.fp ppf ",@ ";
Pp.fp ppf {|@[<hov 2>"%s"@ :@ %a@]|} (show name) (json defs sch) x;
Pp.fp ppf "%a" (json_obj true defs q) xs
| (Opt,_,_) :: q, (_, None ) :: xs ->
json_obj not_first defs q ppf xs
end
let pretty_json x = Pretty_json.json Closed x
module Simple_json = struct
let rec json: type a f. f pending_rec_def -> (a,f) s -> Format.formatter -> a -> unit =
fun defs sch ppf x -> match sch, x with
| Int, n -> Pp.fp ppf "%d" n
| Float, f -> Pp.fp ppf "%f" f
| String, s -> Pp.estring ppf s
| Bool, b -> Pp.fp ppf "%b" b
| Void, _ -> .
| Array k, l ->
Pp.fp ppf "@[<v>[%a]@]"
(Pp.list ~sep:(Pp.s ",@ ") @@ json defs k) l
| [], [] -> ()
| _ :: _ as sch , l -> Pp.fp ppf "@[<h>[%a]@]" (tuple defs sch) l
| Obj sch, x -> Pp.fp ppf "@[<v>{@ %a@ }@]" (obj false defs sch) x
| Custom c, x -> json defs c.sch ppf (c.fwd x)
| Sum q, x -> sum 0 defs q ppf x
| Description(_,sch), x -> json defs sch ppf x
| Var n, x ->
let Pending p = defs in
json defs (get p.defs n) ppf x
| Rec { defs; id; proj; _ }, x -> json (Pending {defs; id}) (get defs proj) ppf x
and sum: type all x. int -> all pending_rec_def -> (x,all) sum_decl ->
Format.formatter -> x sum -> unit =
fun n defs sch ppf x -> match sch, x with
| (n,a) :: _ , C Z x ->
let module N = Label(struct let l=n end) in
json defs (Obj [Req, N.l, a]) ppf (Record.[N.l, x])
| (n,_) :: _ , C E ->
json defs String ppf n
| _ :: q, C S c -> sum (n+1) defs q ppf (C c)
| [], _ -> .
and tuple: type a f. f pending_rec_def -> (a tuple,f) s -> Format.formatter -> a tuple -> unit =
fun defs sch ppf x -> match sch, x with
| [], [] -> ()
| [a], [x] -> json defs a ppf x
| a :: q, x :: xs -> Pp.fp ppf "%a,@ %a" (json defs a) x (tuple defs q) xs
| Custom _, _ -> assert false
| Description _, _ -> assert false
| Rec { defs; proj; id; _ }, x -> tuple (Pending {id;defs}) (get defs proj) ppf x
| Var proj, x ->
let Pending p = defs in
tuple defs (get p.defs proj) ppf x
and obj: type a r.
bool -> r pending_rec_def -> (a,r) record_declaration -> Format.formatter -> a record -> unit =
fun not_first defs sch ppf x -> match sch, x with
| [], [] -> ()
| (Req, name,sch) :: q , (_, x) :: xs ->
if not_first then Pp.fp ppf ",@ ";
Pp.fp ppf {|@[<v 2>"%s":@ %a@]|} (show name) (json defs sch) x;
Pp.fp ppf "%a" (obj true defs q) xs
| (Opt,name,sch) :: q, (_,Some x) :: xs ->
if not_first then Pp.fp ppf ",@ ";
Pp.fp ppf {|@[<v 2>"%s":@ %a@]|} (show name) (json defs sch) x;
Pp.fp ppf "%a" (obj true defs q) xs
| (Opt,_,_) :: q, (_, None ) :: xs ->
obj not_first defs q ppf xs
end
let simple_json x = Simple_json.json Closed x
let cstring ppf s =
begin try
ignore(String.index s ' ');
Pp.estring ppf s
with
Not_found -> Pp.string ppf s
end
let rec sexp: type a all. all pending_rec_def -> (a,all) s -> Format.formatter -> a -> unit =
fun defs sch ppf x -> match sch, x with
| Int, n -> Pp.fp ppf "%d" n
| Float, f -> Pp.fp ppf "%f" f
| Bool, b -> Pp.fp ppf "%b" b
| String, s -> cstring ppf s
| Void, _ -> .
| Array k, l ->
Pp.fp ppf "@[<hov>(%a)@]"
(Pp.list ~sep:(Pp.s "@ ") @@ sexp defs k) l
| Obj sch, x -> Pp.fp ppf "@[<hov>(@;<1 2>%a@;<1 2>)@]" (sexp_obj defs sch) x
| [], [] -> ()
| _ :: _ as tu, t -> Pp.fp ppf "@[<hov>(@;%a@;)@]" (sexp_tuple defs tu) t
| Custom r, x -> sexp defs r.sch ppf (r.fwd x)
| Sum s, x -> sexp_sum 0 defs s ppf x
| Description(_,sch), x -> sexp defs sch ppf x
| Rec { defs; proj; id }, x ->
sexp (Pending {defs;id}) (get defs proj) ppf x
| Var proj, x ->
let Pending p = defs in
sexp defs (get p.defs proj) ppf x
and sexp_tuple: type all a. all pending_rec_def -> (a Tuple.t,all) s -> Format.formatter -> a Tuple.t -> unit =
fun defs ty ppf t -> match ty, t with
| [], [] -> ()
| [a], [x] -> sexp defs a ppf x
| a :: q, x :: xs -> Pp.fp ppf "%a@ %a" (sexp defs a) x (sexp_tuple defs q) xs
| Custom _, _ -> assert false
| Description _, _ -> assert false
| Rec { defs; proj; id }, x ->
sexp_tuple (Pending {id;defs}) (get defs proj) ppf x
| Var proj, x ->
let Pending p = defs in
sexp_tuple defs (get p.defs proj) ppf x
and sexp_sum: type all a. int -> all pending_rec_def -> (a, all) sum_decl -> Format.formatter ->
a sum -> unit =
fun n defs decl ppf x -> match decl, x with
| (n,a) :: _ , C Z x -> sexp defs [String;a] ppf Tuple.[n;x]
| (n,_) :: _ , C E -> sexp defs String ppf n
| _ :: q, C S c -> sexp_sum (n+1) defs q ppf (C c)
| [] , _ -> .
and sexp_obj: type a all.
all pending_rec_def -> (a,all) record_declaration -> Format.formatter -> a record -> unit =
fun defs sch ppf x -> match sch, x with
| [], [] -> ()
| (Req, name,sch) :: q , (_, x) :: xs ->
Pp.fp ppf {|(%a@ %a)|} cstring (show name) (sexp defs sch) x;
begin match q, xs with
| [], [] -> ()
| _ -> Pp.fp ppf "@ %a" (sexp_obj defs q) xs
end
| (Opt, name,sch) :: q , (_, Some x) :: xs ->
Pp.fp ppf {|(%a@ %a)|} cstring (show name) (sexp defs sch) x;
begin match q, xs with
| [], [] -> ()
| _ -> Pp.fp ppf "@ %a" (sexp_obj defs q) xs
end
| (Opt,_,_) :: q, (_, None ) :: xs -> sexp_obj defs q ppf xs
let sexp x = sexp Closed x
let skip name = name, None
let ($=?) field x = match x with
| Some x -> field $= Some x
| None -> skip field
let obj (x:_ record)= x
module Untyped = struct
type t =
| Array of t list
| List of t list
| Atom of string
| Obj of (string * t) list
type untyped = t
end
type untyped = Untyped.t =
| Array of untyped list
| List of untyped list
| Atom of string
| Obj of (string * untyped) list
let promote_to_obj l =
let promote_pair = function List [Atom x;y] -> Some(x,y) | _ -> None in
Option.List'.map promote_pair l
let rec retype: type a f. f pending_rec_def -> (a,f) s -> untyped -> a option =
let open Option in
fun defs sch u -> match sch, u with
| Int, Atom u -> Support.opt int_of_string u
| Float, Atom u -> Support.opt float_of_string u
| Bool, Atom u -> Support.opt bool_of_string u
| String, Atom s -> Some s
| Array t, (Array ul | List ul) ->
Option.List'.map (retype defs t) ul
| [], Array [] -> Some []
| (a::q), (Array(ua :: uq) | List(ua :: uq)) ->
retype defs a ua >>= fun h ->
retype defs q (Array uq) >>| fun q ->
Tuple.(h :: q)
| Obj r, Obj ur ->
retype_obj defs r ur
| Obj r, List ur ->
promote_to_obj ur >>= fun obj ->
retype_obj defs r obj
| Custom r, x -> retype defs r.sch x >>| r.rev
| Sum s, Atom x ->
retype_const_sum s x
| Sum s, (Obj[x,y]|List[Atom x;y]) ->
retype_sum defs s x y
| Rec {defs;proj; id}, x ->
retype (Pending {defs;id}) (get defs proj) x
| Var proj, x ->
let Pending p = defs in
retype defs (get p.defs proj) x
| _ -> None
and retype_obj: type a f. f pending_rec_def -> (a,f) record_declaration -> (string * untyped) list ->
a record option = fun defs sch x ->
let open Option in
match sch, x with
| [], [] -> Some []
| (Req, field, t) :: q , (ufield, u) :: uq when show field = ufield ->
retype defs t u >>= fun h ->
retype_obj defs q uq >>| fun l ->
Record.( (field $= h) :: l )
| (Opt, field, t) :: q , (ufield, u) :: uq when show field = ufield ->
retype defs t u >>= fun h ->
retype_obj defs q uq >>| fun l ->
Record.( (field $=? Some h) :: l )
| (Opt,field,_) :: q , l ->
retype_obj defs q l >>| fun l ->
Record.( skip field :: l )
| _ -> None
and retype_sum: type all a. all pending_rec_def -> (a,all) sum_decl -> string
-> untyped -> a sum option =
let open Option in
fun defs decl n u ->
match decl with
| (s, a) :: _ when s = n ->
retype defs a u >>| fun a -> C(Z a)
| [] -> None
| _ :: q ->
retype_sum defs q n u >>| fun (C c) -> (C (S c))
and retype_const_sum: type a b. (a,b) sum_decl -> string -> a sum option =
let open Option in
fun decl n -> match decl with
| ( (s, Void) :: _ ) when s = n -> Some (C E)
| [] -> None
| _ :: q ->
retype_const_sum q n >>| fun (C c) -> (C (S c))
let retype x = retype Closed x
let minify ppf =
let f = Format.pp_get_formatter_out_functions ppf () in
let space_needed = ref false in
let out_string s start stop =
let special c =
match c with
| '(' | ',' |'{' | '"' |'[' | ')'| ']'| '}' -> true
| _ -> false in
if !space_needed && not (special s.[start]) then
f.out_string " " 0 1;
f.out_string s start stop;
space_needed := not (special s.[stop-1]) in
let basic = Format_compat.transform f out_string in
Format.pp_set_formatter_out_functions ppf basic;
Format.kfprintf (fun _ -> Format.pp_set_formatter_out_functions ppf f;
Format.pp_print_flush ppf ()) ppf
let default x y = if x = y then None else Some y
let option (type a f) (sch: (a,f) s) =
custom (Sum ["None", Void; "Some", sch])
(function None -> C E | Some x -> C (S(Z x)))
(function C E -> None | C S Z x -> Some x | C S E -> None | _ -> . )
let pair (type a b f) (x: (a,f) s) (y: (b,f) s) =
custom [x;y]
(fun (x,y) -> Tuple.[x;y])
Tuple.(fun [x;y] -> x, y )
let (<?>) x y = Description(y,x)
module Ext = struct
type ('lbl,'a) ext = {
title: string;
description: string;
version: Version.t;
label: 'lbl label;
inner: 'a t;
}
type 'a diff = {expected: 'a; got:'a}
type error =
| Future_version of Version.t diff
| Mismatched_kind of string diff
| Unknown_format
| Parse_error
type bound = B: 'a t * 'a -> bound
let bind sch x = B(sch,x)
let schema_gen (ext: (_,_) ext): _ t =
(Obj [ Req, Version.Lbl.l, Version.sch; Req, ext.label, ext.inner ])
let schema_obj: _ t -> _ t = function
| Obj r -> Obj ((Req, Version.Lbl.l, Version.sch) :: r)
| _ -> assert false
let schema_custom ext =
match ext.inner with
| Custom {fwd;rev;sch=(Obj _ as sch)} ->
let sch = schema_obj sch in
let rev ( (l,v) :: q : _ record): _ record =
[l $= v; ext.label $= rev q] in
let fwd ([ (lv,v); (_,x)]: _ record) =
((lv $= v) :: fwd x: _ record) in
Custom {fwd; rev; sch}
| _ -> assert false
let schema (type a b) (sch: (a,b) ext) = match sch.inner with
| Obj _ as x -> Dyn (Closed,schema_obj x)
| Custom { sch = Obj _; _} -> Dyn(Closed,schema_custom sch)
| _ -> Dyn (Closed,schema_gen sch)
let extend (type a b) (sch: (a,b) ext) (x: b) = match sch.inner, x with
| Obj _ as s , x ->
bind (schema_obj s)
((Version.Lbl.l $= sch.version) :: x)
| Custom { sch = Obj _; _ }, x -> bind (schema_custom sch)
[Version.Lbl.l $= sch.version; sch.label $= x ]
| _, x ->
bind (schema_gen sch)
[ Version.Lbl.l $= sch.version; sch.label $= x ]
let pretty_json s ppf x =
let B (sch, x) = extend s x in
pretty_json sch ppf x
let simple_json s ppf x =
let B (sch, x) = extend s x in
simple_json sch ppf x
let json_schema ppf s =
let Dyn (rctx,sch) = schema s in
let {ctx; map; _ } = extract_def rctx sch in
Pp.fp ppf
"@[<v 2>{@ \
%a,@;\
%a,@;\
%a,@;\
@[%a :@ {%a},@]@;\
%a\
@ }@]@."
p ("$schema", "http://json-schema.org/schema#")
p ("title", s.title)
p ("description", s.description)
k "definitions" (json_definitions ctx.mapped) map
(json_type ~recs:rctx ctx.mapped L.[]) sch
let sexp s ppf x =
let B(s,x) = extend s x in
sexp s ppf x
let optr = function
| None -> Error Parse_error
| Some x -> Ok x
let opt f x y = optr (f x y)
let rec strict s = let open Mresult.Ok in
function
| Obj [ version, v; name, data ]
when version = show Version.Lbl.l && name = show s.label ->
opt retype Version.sch v >>= fun v ->
if v = s.version then
opt retype s.inner data
else Error (Future_version { expected=s.version; got=v })
| List l ->
optr (promote_to_obj l) >>= fun ol -> strict s (Obj ol)
| Obj [ version, _; name, _ ] when version = show Version.Lbl.l ->
Error (Mismatched_kind { expected = show s.label; got = name } )
| Array _ | Atom _ | Obj _ -> Error Unknown_format
type ('a,'b) t = ('a,'b) ext
end