-
Notifications
You must be signed in to change notification settings - Fork 36
/
piqi.ml
2420 lines (2016 loc) · 75 KB
/
piqi.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
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
(*
Copyright 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2017, 2018 Anton Lavrik
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*)
module C = Piqi_common
open C
module Idtable = Piqi_db.Idtable
type idtable = T.typedef Idtable.t
(* start in boot_mode by default, it will be switched off later (see below) *)
let is_boot_mode = ref true
(* resolved type definition for the Piqi language;
* it will be appropriately initialized during boot stage (see below) *)
let piqi_lang_def :T.piqtype ref = ref `bool
(* resolved type definition for the Piqi specification *)
let piqi_spec_def :T.piqtype ref = ref `bool
(* resolved type definition for the Piq AST node specification *)
let piq_def :T.piqtype ref = ref `bool
(* resolved "typedef" type definition
* Will be appropriately initialized during boot stage (see below) *)
let typedef_def :T.piqtype ref = ref `bool
let field_def :T.piqtype ref = ref `bool
let option_def :T.piqtype ref = ref `bool
let function_def :T.piqtype ref = ref `bool
let import_def :T.piqtype ref = ref `bool
(* loaded in boot () -- see below *)
let piqi_spec :T.piqi option ref = ref None
let piqi_lang :T.piqi option ref = ref None
let piq_spec :T.piqi option ref = ref None
let is_boot_piqi piqi =
match !piqi_spec with
| Some x -> x == piqi
| None -> false
(* processing hooks to be run at the end of Piqi module load & processing *)
let processing_hooks = ref []
let register_processing_hook (f :idtable -> T.piqi -> unit) =
debug "register_processing_hook(0)\n";
(* NOTE: create an empty idtable just to make invocations below work; none of
* the plugins actually require a valid idtable to exist at this point, so we
* don't care *)
let idtable = Idtable.empty in
(* run the hook on the embedded Piqi self-specification *)
debug "register_processing_hook(1.5)\n";
f idtable (some_of !piqi_lang);
debug "register_processing_hook(1.6)\n";
f idtable (some_of !piqi_spec);
debug "register_processing_hook(1.7)\n";
f idtable (some_of !piq_spec);
debug "register_processing_hook(1)\n";
(* add the hook to the list of registered hooks *)
processing_hooks := !processing_hooks @ [f]
let add_typedef idtable (typedef:T.typedef) =
let name = C.typedef_name typedef in
debug "add_typedef: %s\n" name;
if Idtable.mem idtable name
then (
let prev_def = Idtable.find idtable name in
if C.is_builtin_def prev_def
then (
(* allowing to override a boot def *)
C.warning typedef ("override of built-in type definition " ^ U.quote name);
Idtable.add idtable name typedef
)
else
C.error typedef
("duplicate type definition " ^ U.quote name ^ "\n" ^
error_string prev_def "first defined here")
)
else
Idtable.add idtable name typedef
let add_typedefs idtable defs =
List.fold_left add_typedef idtable defs
let add_imported_typedef idtable (typedef:T.typedef) =
let open Import in
let import =
match get_parent typedef with
| `import x -> x
| _ -> assert false
in
(* while adding imported defs to the idtable, transform definition's names to
* contain module's namespace *)
let name = some_of import.name ^ "/" ^ C.typedef_name typedef in
debug "add_imported_typedef: %s\n" name;
Idtable.add idtable name typedef
let add_imported_typedefs idtable defs =
List.fold_left add_imported_typedef idtable defs
let find_def idtable name =
try Idtable.find idtable name
with Not_found ->
error name ("unknown type " ^ U.quote name)
let is_func_param def =
match def with
| `record x -> x.R.is_func_param
| `variant x -> x.V.is_func_param
| `enum x -> x.E.is_func_param
| `alias x -> x.A.is_func_param
| `list x -> x.L.is_func_param
(* mark type definition as a function parameter *)
let set_is_func_param_flag def =
match def with
| `record x -> x.R.is_func_param <- true
| `variant x -> x.V.is_func_param <- true
| `enum x -> x.E.is_func_param <- true
| `alias x -> x.A.is_func_param <- true
| `list x -> x.L.is_func_param <- true
let resolve_typename map name :T.piqtype =
let def = find_def map name in
(def :> T.piqtype)
(* XXX: is there a way to avoid code duplicaton here? *)
let resolve_field_typename map f =
let open F in
match f.typename with
| None -> () (* flag *)
| Some name ->
f.piqtype <- Some (resolve_typename map name)
let resolve_option_typename map o =
let open O in
match o.typename with
| None -> ()
| Some name ->
o.piqtype <- Some (resolve_typename map name)
let resolve_def_typenames map = function
| `record r ->
List.iter (resolve_field_typename map) r.R.field
| `variant v ->
List.iter (resolve_option_typename map) v.V.option
| `alias a ->
let piqtype =
match a.A.typename with
| Some name ->
resolve_typename map name
| None ->
(* either one or another must be defined -- this has been checked
* earlier in check_alias *)
let piqi_type = some_of a.A.piqi_type in
(piqi_type :> T.piqtype)
in
a.A.piqtype <- Some piqtype
| `list l ->
l.L.piqtype <- Some (resolve_typename map l.L.typename)
| `enum _ ->
()
let check_name x =
if not (Piqi_name.is_valid_name x)
then error x ("invalid name: " ^ U.quote x)
else ()
let check_opt_name = function
| None -> ()
| Some x -> check_name x
let check_dup_names what names =
match U.find_dups names with
| None -> ()
| Some (name, prev) ->
error name
("duplicate " ^ what ^ " name " ^ U.quote name ^ "\n" ^
error_string prev "first defined here")
let check_field f =
let open Field in
begin
begin
check_opt_name f.name;
match f.name, f.typename with
| None, None ->
error f "name or type must be specified for a field"
| Some _, None -> (* flag *)
begin
(if f.mode <> `optional
then error f "flags must be optional");
(if f.default <> None
then error f "flags may not specify default");
end
| _ ->
()
end;
if f.default <> None && f.mode <> `optional
then
error f.default "default values may only be specified for optional fields"
end
let check_def_name obj = function
| Some x -> check_name x
| None ->
error obj "missing field \"name\""
let check_record r =
check_def_name r r.R.name;
let fields = r.R.field in
(* XXX: protoc doesn't print any warnings on records with no fields *)
List.iter check_field fields
let check_option o =
let open Option in
begin
check_opt_name o.name;
match o.name, o.typename with
| None, None ->
error o "name or type must be specified for an option"
| _ -> ()
end
let check_variant v =
check_def_name v v.V.name;
let name = some_of v.V.name in
let options = v.V.option in
if options = []
then error v ("variant " ^ U.quote name ^ " doesn't specify any options");
List.iter check_option options
let check_enum_option x =
let open O in
begin
(* consts name must be defined and types must not *)
if x.name = None
then error x ("enum options must specify name");
if x.typename <> None
then error x ("enum options must not specify type");
check_opt_name x.name;
end
let check_enum e =
check_def_name e e.E.name;
let name = some_of e.E.name in
let options = e.E.option in
if options = []
then error e ("enum " ^ U.quote name ^ " doesn't specify any options");
List.iter check_enum_option options
let check_alias a =
let open A in
begin
check_def_name a a.name;
(* TODO: this check is incomplete *)
let name = some_of a.name in
if a.typename = None && a.piqi_type = None
then
error a ("alias " ^ U.quote name ^ " must specify either piqi-type or type");
end
let check_list l =
let open L in
begin
check_def_name l l.name;
end
let check_def def =
match def with
| `record x -> check_record x
| `variant x -> check_variant x
| `enum x -> check_enum x
| `alias x -> check_alias x
| `list x -> check_list x
let check_resolved_alias a =
let open A in
begin
(match some_of a.piqtype with
| `alias b ->
if a.piqi_type <> None && a.piqi_type <> b.piqi_type
then error a "piqi-type doesn't match piqi-type in the redefined alias"
| #T.piqi_type -> () (* piqtype was derived from piqi_type *)
| _ when a.piqi_type <> None -> (* other definitions *)
error a "piqi-type can not be specified for an non-alias"
| _ -> ()
);
end
let check_resolved_def def =
match def with
| `record x ->
let names = List.map (fun x -> name_of_field x) x.R.field in
check_dup_names "field" names
| `variant x ->
let names = List.map (fun x -> name_of_option x) x.V.option in
(* TODO: also check duplicate names among nested variants (i.e.
* "non-terminal" nameless sub-variants) *)
check_dup_names "option" names
| `enum x ->
let names = List.map (fun x -> name_of_option x) x.E.option in
check_dup_names "enum option" names
| `alias x ->
check_resolved_alias x
| _ -> ()
(* check that there are no infinite types among the definitions *)
let check_no_infinite_types ~parent defs =
(* DFS graph traversal *)
let black = ref [] in (* visited nodes, i.e. after last_visit *)
let grey = ref [] in (* nodes between first_visit and last_visit *)
let set_color node = function
| `grey ->
grey := node::!grey
| color -> (* white or black *)
(* first, reset grey *)
(match !grey with
| h::t when h == node ->
grey := t
| _ ->
assert false
);
if color = `black
then
black := node::!black
in
let get_color node =
if List.memq node !black
then `black
else if List.memq node !grey
then `grey
else `white
in
let is_local_type piqtype =
match piqtype with
| (#T.typedef as x) ->
C.get_parent x == parent
| _ -> (* considering built-in types local -- they can't be infinite *)
true
in
(* we have to backtrack in order to correctly check variant type infinity, so
* we report the infinite type error at the very end *)
let infinite_type = ref None in
let rec finite_path_exists piqtype =
let finite = true in
let infinite err =
infinite_type := Some (piqtype, err);
false
in
match get_color piqtype with
| `black -> (* already processed, which means the type is finite *)
true
| `grey -> (* found a cycle => the type is infinite *)
let err =
match piqtype with
| `alias x ->
(* NOTE: there is a reason why we are handling it here -- see
* below *)
"alias " ^ U.quote (some_of x.A.name) ^ " forms a loop"
| _ ->
(* the path is infinite, but the error (if any) will be
* reported elsewhere *)
""
in
infinite err
| `white -> (* unseen node *)
set_color piqtype `grey; (* mark the node as being processed *)
let res =
if not (is_local_type piqtype)
then
(* no need to check imported types as they've been checked already *)
finite
else
match piqtype with
| `record x ->
let rec check_fields = function
| [] ->
finite (* all fields are finite *)
| f::tail ->
(match f.F.piqtype with
| Some piqtype when f.F.mode = `required ->
(* NOTE: it is OK for optional and repeated
* field to form a loop; for instance, variant
* -> record expansion can produce such options
* naturally; similarly, some .proto files (e.g.
* descriptor.proto) have repeated fields
* forming a loop *)
if not (finite_path_exists piqtype)
then
infinite (
"record " ^ U.quote (some_of x.R.name) ^
" is an infinite type (field " ^ U.quote (name_of_field f) ^ " forms a loop)"
)
else
check_fields tail
| _ ->
check_fields tail
)
in
check_fields x.R.field
| `variant x ->
(* there's a finite path for a variant if there's a finite path
* for at least one of its options (option with no type means
* finite type by definition) *)
let options = x.V.option in
let is_variant_finite =
if List.exists (fun x -> x.O.piqtype = None) options
then true
else List.exists (fun x -> finite_path_exists (some_of x.O.piqtype)) options
in
if not is_variant_finite
then infinite ("variant " ^ U.quote (some_of x.V.name) ^ " is an infinite type (each option forms a loop)")
else finite
| `list x ->
if not (finite_path_exists (some_of x.L.piqtype))
then infinite ("list " ^ U.quote (some_of x.L.name) ^ " forms a loop")
else finite
| `alias x ->
(* NOTE: not reporting an alias loop here, so that it
* doesn't take precedence over loop reports for records,
* variants and lists when there's an alias in the middle *)
finite_path_exists (some_of x.A.piqtype)
| _ -> (* enum and primitive types are finite *)
finite
in
(* mark the node as processed if the path is finite or unprocessed is
* the type is infinite as we are backtracking *)
if res
then set_color piqtype `black
else set_color piqtype `white;
res
in
let check_typedef x =
if not (finite_path_exists (x :> T.piqtype))
then
(* reporting infinite type error *)
let piqtype, err = some_of !infinite_type in
error piqtype err
in
(* process non-variants first, because they may have tighter loops that would
* otherwise be reported as variant loops *)
let variants, non_variants =
List.partition (function `variant _ -> true | _ -> false) defs
in
List.iter check_typedef (non_variants @ variants)
(* scoped extensions names should have exactly two sections separated by '.' *)
let is_valid_scoped_extension_name name =
if not (Piqi_name.is_valid_name name ~allow:".")
then false
else
match U.string_split name '.' with
| [a; b] -> Piqi_name.is_valid_name a && Piqi_name.is_valid_name b
| _ -> false
let check_extension_name = function
| `name name | `typedef name | `import name | `func name ->
if not (Piqi_name.is_valid_name name)
then error name "invalid extension name"
| `field name | `option name ->
if not (is_valid_scoped_extension_name name)
then error name "invalid scoped extension name"
let check_extension_spec spec =
check_extension_name spec;
match spec with
| `name name ->
C.warning name "use of .name for extending typedefs is deprecated; use .typedef instead"
| _ -> ()
let check_extension x =
let open Extend in
begin
if x.what = [] && x.piqi_with = []
then error x ("extension doesn't specify any names");
if x.quote = [] && x.piqi_with = []
then error x ("extension doesn't specify any extensions");
if x.quote <> []
then C.warning (List.hd x.quote) "this style of extensions is deprecated; always use .with prefix";
List.iter check_extension_spec x.what
end
let debug_loc prefix =
debug "%s out count = %d, in count = %d\n" prefix !Piqloc.ocount !Piqloc.icount
let assert_loc () =
if (!Piqloc.ocount <> !Piqloc.icount)
then
let s = Printf.sprintf "internal_error: out count = %d, in count = %d\n" !Piqloc.ocount !Piqloc.icount in
(*
failwith s
*)
piqi_warning s
let add_fake_loc (obj :Piqobj.obj) =
Piqloc.do_add_fake_loc obj ~label:"_self_piqi_default";
match obj with
| `enum x ->
Piqloc.do_add_fake_loc x ~label:"_self_piqi_default_enum"
| _ ->
()
let resolve_default_value piqi_any piqtype =
assert_loc ();
debug_loc "resolve_default_value(0)";
let any = Piqobj.any_of_piqi_any piqi_any in
Piqobj.resolve_obj any ~piqtype;
(* make sure we add fake locations for the default values of the
* embedded self-specifications; currently, there's only one such value
* which is field.mode = required *)
if !is_boot_mode
then add_fake_loc (some_of any.Piqobj.Any.obj);
(* NOTE: fixing (preserving) location counters which get skewed during
* parsing defaults *)
Piqloc.icount := !Piqloc.ocount;
debug_loc "resolve_default_value(1)";
()
let resolve_field_default x =
let open F in
match x.default, x.piqtype with
| None, _ -> () (* no default *)
| Some piqi_any, Some piqtype ->
debug "resolve_field_default: %s\n" (C.name_of_field x);
resolve_default_value piqi_any piqtype
| _ ->
assert false
let resolve_field_piq_flag_default x =
let open F in
match x.piq_flag_default, x.piqtype with
| None, _ -> () (* no default *)
| Some piqi_any, Some piqtype ->
debug "resolve_field_default: %s\n" (C.name_of_field x);
resolve_default_value piqi_any piqtype
| _ ->
assert false
let resolve_defaults = function
| `record x ->
List.iter resolve_field_default x.R.field;
List.iter resolve_field_piq_flag_default x.R.field
| _ ->
()
let copy_obj (x:'a) :'a =
Obj.obj (Obj.dup (Obj.repr x))
let copy_obj x = reference copy_obj x
let copy_obj_list l = List.map copy_obj l
let copy_variant ?(copy_parts=true) x =
if copy_parts
then Piqloc.addrefret x V.({x with option = copy_obj_list x.option})
else copy_obj x
let copy_enum ?(copy_parts=true) x =
if copy_parts
then Piqloc.addrefret x E.({x with option = copy_obj_list x.option})
else copy_obj x
let copy_record ?(copy_parts=true) x =
if copy_parts
then Piqloc.addrefret x R.({x with field = copy_obj_list x.field})
else copy_obj x
let copy_def ~copy_parts (x:T.typedef) =
let res =
match x with
| `record x -> `record (copy_record ~copy_parts x)
| `variant x -> `variant (copy_variant ~copy_parts x)
| `enum x -> `enum (copy_enum ~copy_parts x)
| `alias x -> `alias (copy_obj x)
| `list x -> `list (copy_obj x)
in
(* preserve location information *)
Piqloc.addrefret x res
let copy_defs ?(copy_parts=true) defs = List.map (copy_def ~copy_parts) defs
let copy_imports l = List.map copy_obj l
let transform_flag x =
let open F in
if x.typename = None
then (
x.typename <- Some "bool";
x.default <- Some (Piqobj.make_piqi_any_from_obj (`bool false));
);
if x.typename = Some "bool" && x.piq_flag_default = None
then (
x.piq_flag_default <- Some (Piqobj.make_piqi_any_from_obj (`bool true));
)
let transform_flags = function
| `record x ->
List.iter transform_flag x.R.field
| _ ->
()
let resolve_defs ~piqi idtable (defs:T.typedef list) =
(*
(* a fresh copy of defs is needed, since we can't alter the original ones:
* we need to resolve types & assign codes in order to resolve_defaults *)
*)
(* check definitions validity *)
List.iter check_def defs;
(* transform piq flags (i.e. .name ... .optional) into boolean fields:
*
* .name ... .optional .type bool .default false .piq-flag-default true
*)
List.iter transform_flags defs;
(* add definitions to the map: def name -> def *)
let idtable = add_typedefs idtable defs in
(* resolve type names using the map *)
List.iter (resolve_def_typenames idtable) defs;
(* set up parent namespace to local piqi defs *)
let parent = `piqi piqi in
List.iter (fun def -> set_parent def parent) defs;
(* check records, variants, enums for duplicate field/option names; check wire
* types in aliases *)
List.iter check_resolved_def defs;
(* check that there are no infinite types among the definitions *)
check_no_infinite_types defs ~parent;
(* assign wire codes, if they are unassigned; check otherwise; check
* correctness of .wire-packed usage *)
Piqi_protobuf.process_typedefs defs;
(* run some checks and expansions specific to the Piq format *)
Piq.process_typedefs defs;
(* return updated idtable *)
idtable
let check_defs ~piqi idtable defs =
ignore (resolve_defs idtable (copy_defs defs) ~piqi)
let read_piqi_common fname piq_parser :piq_ast =
(* NOTE: not expanding abbreviations until we construct the containing object *)
let res = Piq_parser.read_all piq_parser in
if res = []
then piqi_warning ("piqi file is empty: " ^ fname);
(* wrapping items in list to make them contents of "piqi" record *)
let res = `list res in
let startloc = (fname, 1, 1) in (* start location *)
let ast = Piqloc.addlocret startloc res in
(* now expand abbreviations *)
Piq_parser.expand ast
let read_piqi_channel fname ch :piq_ast =
(* XXX: handle read errors *)
let piq_parser = Piq_parser.init_from_channel fname ch in
read_piqi_common fname piq_parser
let read_piqi_string fname content :piq_ast =
let piq_parser = Piq_parser.init_from_string fname content in
read_piqi_common fname piq_parser
let open_piqi fname =
try Pervasives.open_in_bin fname
with Sys_error s ->
piqi_error ("error opening piqi file: " ^ s)
let read_piqi_file fname :piq_ast =
let ch = open_piqi fname in
let res =
try read_piqi_channel fname ch
with x -> (* try ... after *)
Pervasives.close_in ch;
raise x
in
Pervasives.close_in ch;
res
let check_modname x =
if Piqi_name.is_valid_modname x
then ()
else error x ("invalid piqi module name: " ^ x)
let check_assign_module_name ?modname fname (piqi:T.piqi) =
let open P in
match piqi.modname, modname with
| Some x, Some x' ->
check_modname x;
(* check that the requested module name corresponds to the module name
* defined in the file *)
if x <> x'
then
error piqi
("module loaded as " ^ U.quote x' ^
" has different name " ^ U.quote x)
else ()
| Some x, None -> (* name is already defined for the module *)
check_modname x
| None, Some x ->
piqi.modname <- modname
| None, None ->
(* basename + chop .piqi and .proto.piqi extensions *)
let basename = Piqi_file.basename fname in
if Piqi_name.is_valid_modname basename
then piqi.modname <- Some basename
else error piqi "piqi module name can not be derived from the file name"
let assign_import_name x =
let open Import in
match x.name with
| Some x -> (* import name is already defined *)
check_name x
| None ->
(* derive import name from the original module's name *)
let name = Piqi_name.get_local_name x.modname in
x.name <- Some name
let name_of_import x =
let open Import in
match x.name with
| Some x -> x (* import name is already defined *)
| None ->
(* derive import name from the original module's name *)
Piqi_name.get_local_name x.modname
let mlobj_to_piqobj piqtype wire_generator mlobj =
debug_loc "mlobj_to_piqobj(0)";
assert_loc ();
let binobj = Piqirun.gen_binobj wire_generator mlobj in
debug_loc "mlobj_to_piqobj(1.5)";
(* don't' resolve defaults when reading wire *)
let piqobj =
U.with_bool C.is_inside_parse_piqi true
(fun () ->
C.with_resolve_defaults false (fun () -> Piqobj_of_protobuf.parse_binobj piqtype binobj)
)
in
debug_loc "mlobj_to_piqobj(1)";
assert_loc ();
piqobj
let mlobj_to_ast piqtype wire_generator mlobj =
debug_loc "mlobj_to_ast(0)";
let piqobj = mlobj_to_piqobj piqtype wire_generator mlobj in
debug_loc "mlobj_to_ast(1.5)";
let ast = Piqobj_to_piq.gen_obj piqobj in
debug_loc "mlobj_to_ast(1)";
assert_loc ();
ast
let mlobj_of_piqobj wire_parser piqobj =
let binobj = Piqobj_to_protobuf.gen_binobj piqobj in
let mlobj = Piqirun.parse_binobj wire_parser binobj in
mlobj
let mlobj_of_ast piqtype wire_parser ast =
debug_loc "mlobj_of_ast(0)";
(*
(* initialize starting location code *)
let max_count = max !T.icount !T.ocount in
T.icount := max_count;
T.ocount := max_count;
*)
(* We have to resolve defaults while reading piqi in order to provide correct
* location bindings. It is not possible to "fix" skewed location bindings
* in piqtype.ml after default values get parsed. We rather decided to fix
* location bindings here -- see resolve_defaults function for details *)
let piqobj =
U.with_bool C.is_inside_parse_piqi true
(fun () ->
C.with_resolve_defaults true (fun () -> Piqobj_of_piq.parse_obj piqtype ast)
)
in
debug_loc "mlobj_of_ast(1.5)";
let mlobj = mlobj_of_piqobj wire_parser piqobj in
debug_loc "mlobj_of_ast(1)";
assert_loc ();
mlobj
let parse_piqi ast =
(* XXX: handle errors *)
debug "parse_piqi(0)\n";
(* use prepared static "piqi" definition to parse the ast *)
let res = mlobj_of_ast !piqi_lang_def T.parse_piqi ast in
debug "parse_piqi(1)\n";
res
let is_unknown_field custom_fields x =
match x with
| `named {Piq_ast.Named.name = name} | `name name ->
if List.mem name custom_fields
then false (* field is a custom field, i.e. "known" *)
else true
| _ -> true
let check_unknown_fields ?prepend unknown_fields custom_fields =
let unknown_fields =
List.filter (is_unknown_field custom_fields) unknown_fields
in
let warn x =
(* call the function for printing prepending warning message *)
(match prepend with
| Some f -> f ()
| None -> ());
Piqobj_of_piq.warn_unknown_field x
in
(* print warnings *)
List.iter warn unknown_fields
let parse_scoped_name name =
match U.string_split name '.' with
| [def_name; nested_name] -> def_name, nested_name
| _ -> assert false (* this has been checked already *)
(* replace the first list element for which [f] returns true with [x] *)
let list_replace l f x =
let rec aux accu = function
| [] ->
(* we were supposed to replace an item before we reached the end of the
* list *)
assert false
| h::t ->
if f h
then List.rev_append accu (x::t)
else aux (h::accu) t
in
aux [] l
let name_of_function x = x.T.Func.name
let idtable_of_defs defs =
List.fold_left
(fun t x -> Idtable.add t (C.typedef_name x) x)
Idtable.empty defs
let idtable_of_imports imports =
List.fold_left
(fun t x -> Idtable.add t (name_of_import x) x)
Idtable.empty imports
let idtable_of_functions funcs =
List.fold_left
(fun t x -> Idtable.add t (name_of_function x) x)
Idtable.empty funcs
(* convert the map of extended elements back to list; while doing this, preserve
* the original order *)
let list_of_idtable idtable l name_of_elem =
List.map (fun x -> Idtable.find idtable (name_of_elem x)) l
(* find record field by name *)
let find_field r field_name scoped_name =
try
List.find (fun x -> name_of_field x = field_name) r.R.field
with Not_found ->
error scoped_name ("record doesn't have field named " ^ U.quote field_name)
(* find variant option by name *)
let find_option v option_name scoped_name =
try
List.find (fun x -> name_of_option x = option_name) v.V.option
with Not_found ->
error scoped_name ("variant doesn't have option named " ^ U.quote option_name)
(* replace record field with the new one *)
let replace_field r f field_name =
let fields = r.R.field in
let new_fields = list_replace fields (fun x -> name_of_field x = field_name) f in
Piqloc.addref fields new_fields;
let new_record = R.({r with field = new_fields}) in
Piqloc.addref r new_record;
new_record
(* replace variant option with the new one *)
let replace_option v o option_name =