Skip to content

Commit c59e8e7

Browse files
committed
Parsing of Pb_options according to protobuf schema
1 parent 04b733b commit c59e8e7

19 files changed

+4120
-16
lines changed

src/compilerlib/dune

+6-5
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,10 @@
1313
pb_codegen_make pb_codegen_encode_binary pb_codegen_encode_bs
1414
pb_codegen_encode_yojson pb_codegen_formatting pb_codegen_ocaml_type_dump
1515
pb_codegen_ocaml_type pb_codegen_pp pb_codegen_plugin pb_codegen_types
16-
pb_codegen_services pb_codegen_util pb_exception pb_field_type pb_location
17-
pb_logger pb_option pb_raw_option pb_parsing pb_parsing_lexer
18-
pb_parsing_parser pb_parsing_parse_tree pb_parsing_util pb_typing_graph
19-
pb_typing pb_typing_recursion pb_typing_resolution pb_typing_type_tree
20-
pb_typing_util pb_typing_validation pb_util pb_format_util)
16+
Pb_codegen_decode_pb_options pb_codegen_services pb_codegen_util
17+
pb_exception pb_field_type pb_location pb_logger pb_option pb_raw_option
18+
pb_parsing pb_parsing_lexer pb_parsing_parser pb_parsing_parse_tree
19+
pb_parsing_util pb_typing_graph pb_typing pb_typing_recursion
20+
pb_typing_resolution pb_typing_type_tree pb_typing_util
21+
pb_typing_validation pb_util pb_format_util)
2122
(libraries stdlib-shims))
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,346 @@
1+
module Ot = Pb_codegen_ocaml_type
2+
module F = Pb_codegen_formatting
3+
4+
let sp = Pb_codegen_util.sp
5+
6+
let field_pattern_match ~r_name ~rf_label field_type =
7+
match field_type with
8+
| Ot.Ft_basic_type bt ->
9+
let decode runtime_f =
10+
sp "Pbrt_pb_options.%s pb_options_value \"%s\" \"%s\"" runtime_f r_name
11+
rf_label
12+
in
13+
let exp =
14+
match bt with
15+
| Ot.Bt_string -> decode "string"
16+
| Ot.Bt_float -> decode "float"
17+
| Ot.Bt_int -> decode "int"
18+
| Ot.Bt_int32 -> decode "int32"
19+
| Ot.Bt_int64 -> decode "int64"
20+
| Ot.Bt_uint32 -> sp "`unsigned (%s)" (decode "int32")
21+
| Ot.Bt_uint64 -> sp "`unsigned (%s)" (decode "int64")
22+
| Ot.Bt_bool -> decode "bool"
23+
| Ot.Bt_bytes -> decode "bytes"
24+
in
25+
"pb_options_value", exp
26+
| Ot.Ft_unit ->
27+
( "pb_options_value",
28+
sp "Pbrt_pb_options.unit pb_options_value \"%s\" \"%s\"" r_name rf_label )
29+
| Ot.Ft_user_defined_type udt ->
30+
let f_name =
31+
let function_prefix = "decode_pb_options" in
32+
Pb_codegen_util.function_name_of_user_defined ~function_prefix udt
33+
in
34+
let value_expression = "(" ^ f_name ^ " pb_options_value)" in
35+
"pb_options_value", value_expression
36+
| _ -> assert false
37+
38+
let pb_options_label_of_field_label rf_label =
39+
match rf_label with
40+
| "and_" | "as_" | "assert_" | "begin_" | "class_" | "constraint_" | "do_"
41+
| "done_" | "downto_" | "else_" | "end_" | "exception_" | "external_"
42+
| "false_" | "for_" | "fun_" | "function_" | "functor_" | "if_" | "in_"
43+
| "include_" | "inherit_" | "initializer_" | "lazy_" | "let_" | "match_"
44+
| "method_" | "module_" | "mutable_" | "new_" | "nonrec_" | "object_" | "of_"
45+
| "open_" | "or_" | "private_" | "rec_" | "sig_" | "struct_" | "then_" | "to_"
46+
| "true_" | "try_" | "type_" | "unit_" | "val_" | "virtual_" | "when_"
47+
| "while_" | "with_" | "mod_" | "land_" | "lor_" | "lxor_" | "lsl_" | "lsr_"
48+
| "asr_" ->
49+
String.sub rf_label 0 (String.length rf_label - 1)
50+
| _ -> rf_label
51+
52+
(* Generate all the pattern matches for a record field *)
53+
let gen_rft_nolabel sc ~r_name ~rf_label (field_type, _, _) =
54+
let pb_options_label = pb_options_label_of_field_label rf_label in
55+
56+
let match_variable_name, exp =
57+
field_pattern_match ~r_name ~rf_label field_type
58+
in
59+
F.linep sc "| (\"%s\", %s) -> " pb_options_label match_variable_name;
60+
F.linep sc " v.%s <- %s" rf_label exp
61+
62+
(* Generate all the pattern matches for a repeated field *)
63+
let gen_rft_repeated_field sc ~r_name ~rf_label repeated_field =
64+
let _, field_type, _, _, _ = repeated_field in
65+
66+
let pb_options_label = pb_options_label_of_field_label rf_label in
67+
68+
F.linep sc
69+
"| (\"%s\", Ocaml_protoc_compiler_lib.Pb_option.List_literal l) -> begin"
70+
pb_options_label;
71+
72+
F.sub_scope sc (fun sc ->
73+
F.linep sc "v.%s <- List.map (function" rf_label;
74+
let match_variable_name, exp =
75+
field_pattern_match ~r_name ~rf_label field_type
76+
in
77+
F.linep sc " | %s -> %s" match_variable_name exp;
78+
F.line sc ") l;");
79+
80+
F.line sc "end"
81+
82+
let gen_rft_optional_field sc ~r_name ~rf_label optional_field =
83+
let field_type, _, _, _ = optional_field in
84+
85+
let pb_options_label = pb_options_label_of_field_label rf_label in
86+
87+
let match_variable_name, exp =
88+
field_pattern_match ~r_name ~rf_label field_type
89+
in
90+
91+
F.linep sc "| (\"%s\", %s) -> " pb_options_label match_variable_name;
92+
F.linep sc " v.%s <- Some (%s)" rf_label exp
93+
94+
(* Generate pattern match for a variant field *)
95+
let gen_rft_variant_field sc ~r_name ~rf_label { Ot.v_constructors; _ } =
96+
List.iter
97+
(fun { Ot.vc_constructor; vc_field_type; _ } ->
98+
let pb_options_label =
99+
Pb_codegen_util.camel_case_of_constructor vc_constructor
100+
in
101+
102+
match vc_field_type with
103+
| Ot.Vct_nullary ->
104+
F.linep sc "| (\"%s\", _) -> v.%s <- Some %s" pb_options_label rf_label
105+
vc_constructor
106+
| Ot.Vct_non_nullary_constructor field_type ->
107+
let match_variable_name, exp =
108+
field_pattern_match ~r_name ~rf_label field_type
109+
in
110+
F.linep sc "| (\"%s\", %s) -> " pb_options_label match_variable_name;
111+
F.linep sc " v.%s <- Some (%s (%s))" rf_label vc_constructor exp)
112+
v_constructors
113+
114+
let gen_rft_assoc_field sc ~r_name ~rf_label ~assoc_type ~key_type ~value_type =
115+
let pb_options_label = pb_options_label_of_field_label rf_label in
116+
F.linep sc
117+
"| (\"%s\", Ocaml_protoc_compiler_lib.Pb_option.Message_literal assoc) ->"
118+
pb_options_label;
119+
F.sub_scope sc (fun sc ->
120+
let value_name, value_exp =
121+
field_pattern_match ~r_name ~rf_label value_type
122+
in
123+
let key_name = "key" in
124+
let key_exp =
125+
match key_type with
126+
| Ot.Bt_string -> "key"
127+
| Ot.Bt_int -> "(Int.of_string key)"
128+
| Ot.Bt_int32 -> "(Int32.of_string key)"
129+
| Ot.Bt_int64 -> "(Int64.of_string key)"
130+
| Ot.Bt_uint32 -> "(`unsigned (Int32.of_string key))"
131+
| Ot.Bt_uint64 -> "(`unsigned (Int64.of_string key))"
132+
| Ot.Bt_bool -> "(Bool.of_string key)"
133+
| Ot.Bt_float ->
134+
Printf.eprintf "float cannot be used as a map key type";
135+
exit 1
136+
| Ot.Bt_bytes ->
137+
Printf.eprintf "bytes cannot be used as a map key type";
138+
exit 1
139+
in
140+
F.line sc "let assoc =";
141+
F.sub_scope sc (fun sc ->
142+
F.line sc "assoc";
143+
F.linep sc "|> List.map (fun (%s, %s) -> (%s, %s)) " key_name
144+
value_name key_exp value_exp;
145+
F.line sc "|> List.to_seq";
146+
(* Passing through [Hashtbl.of_seq] even in the [At_list] case ensures that if there
147+
is a repeated key we take the last value associated with it. *)
148+
F.line sc "|> Hashtbl.of_seq");
149+
F.line sc "in";
150+
let assoc_exp =
151+
match assoc_type with
152+
| Ot.At_hashtable -> "assoc"
153+
| Ot.At_list -> "assoc |> Hashtbl.to_seq |> List.of_seq"
154+
in
155+
F.linep sc "v.%s <- %s" rf_label assoc_exp)
156+
157+
(* Generate decode function for a record *)
158+
let gen_record ?and_ { Ot.r_name; r_fields } sc =
159+
let mutable_record_name = Pb_codegen_util.mutable_record_name r_name in
160+
161+
F.line sc
162+
@@ sp "%s decode_pb_options_%s d ="
163+
(Pb_codegen_util.let_decl_of_and and_)
164+
r_name;
165+
166+
F.sub_scope sc (fun sc ->
167+
F.linep sc "let v = default_%s () in" mutable_record_name;
168+
F.line sc @@ "let assoc = match d with";
169+
F.line sc
170+
@@ " | Ocaml_protoc_compiler_lib.Pb_option.Message_literal assoc -> \
171+
assoc";
172+
F.line sc @@ " | _ -> assert(false)";
173+
(* TODO raise E *)
174+
F.line sc @@ "in";
175+
176+
F.line sc "List.iter (function ";
177+
F.sub_scope sc (fun sc ->
178+
(* Generate pattern match for all the possible message field *)
179+
List.iter
180+
(fun { Ot.rf_label; rf_field_type; _ } ->
181+
match rf_field_type with
182+
| Ot.Rft_nolabel nolabel_field ->
183+
gen_rft_nolabel sc ~r_name ~rf_label nolabel_field
184+
| Ot.Rft_repeated repeated_field ->
185+
gen_rft_repeated_field sc ~r_name ~rf_label repeated_field
186+
| Ot.Rft_variant variant_field ->
187+
gen_rft_variant_field sc ~r_name ~rf_label variant_field
188+
| Ot.Rft_optional optional_field ->
189+
gen_rft_optional_field sc ~r_name ~rf_label optional_field
190+
| Ot.Rft_required _ ->
191+
Printf.eprintf
192+
"Only proto3 syntax supported in pb_options encoding";
193+
exit 1
194+
| Ot.Rft_associative
195+
(assoc_type, _, (key_type, _), (value_type, _)) ->
196+
gen_rft_assoc_field sc ~r_name ~rf_label ~assoc_type ~key_type
197+
~value_type)
198+
r_fields;
199+
200+
(* Unknown fields are simply ignored *)
201+
F.empty_line sc;
202+
F.line sc "| (_, _) -> () (*Unknown fields are ignored*)");
203+
F.line sc ") assoc;";
204+
205+
(* Transform the mutable record in an immutable one *)
206+
F.line sc "({";
207+
F.sub_scope sc (fun sc ->
208+
List.iter
209+
(fun { Ot.rf_label; _ } ->
210+
F.linep sc "%s = v.%s;" rf_label rf_label)
211+
r_fields);
212+
F.linep sc "} : %s)" r_name)
213+
214+
(* Generate decode function for an empty record *)
215+
let gen_unit ?and_ { Ot.er_name } sc =
216+
F.line sc
217+
@@ sp "%s decode_pb_options_%s d ="
218+
(Pb_codegen_util.let_decl_of_and and_)
219+
er_name;
220+
F.line sc (sp "Pbrt_pb_options.unit d \"%s\" \"%s\"" er_name "empty record")
221+
222+
(* Generate decode function for a variant type *)
223+
let gen_variant ?and_ { Ot.v_name; v_constructors } sc =
224+
(* helper function for each constructor case *)
225+
let process_v_constructor sc { Ot.vc_constructor; vc_field_type; _ } =
226+
let pb_options_label =
227+
Pb_codegen_util.camel_case_of_constructor vc_constructor
228+
in
229+
230+
match vc_field_type with
231+
| Ot.Vct_nullary ->
232+
F.linep sc "| (\"%s\", _)::_-> (%s : %s)" pb_options_label vc_constructor
233+
v_name
234+
| Ot.Vct_non_nullary_constructor field_type ->
235+
let match_, exp =
236+
let r_name = v_name and rf_label = vc_constructor in
237+
field_pattern_match ~r_name ~rf_label field_type
238+
in
239+
240+
F.linep sc "| (\"%s\", %s)::_ -> " pb_options_label match_;
241+
F.linep sc " (%s (%s) : %s)" vc_constructor exp v_name
242+
in
243+
244+
F.linep sc "%s decode_pb_options_%s pb_options ="
245+
(Pb_codegen_util.let_decl_of_and and_)
246+
v_name;
247+
248+
F.sub_scope sc (fun sc ->
249+
(* even though a variant should be an object with a single field,
250+
* it is possible other fields are present in the pb_options object. Therefore
251+
* we still need a loop to iterate over the key/value, even if in 99.99%
252+
* of the cases it will be a single iteration *)
253+
F.line sc "let assoc = match pb_options with";
254+
F.line sc
255+
" | Ocaml_protoc_compiler_lib.Pb_option.Message_literal assoc -> assoc";
256+
F.line sc " | _ -> assert(false)";
257+
(* TODO raise E *)
258+
F.line sc "in";
259+
260+
F.line sc "let rec loop = function";
261+
F.sub_scope sc (fun sc ->
262+
(* termination condition *)
263+
F.linep sc "| [] -> Pbrt_pb_options.E.malformed_variant \"%s\"" v_name;
264+
265+
List.iter (process_v_constructor sc) v_constructors;
266+
267+
F.empty_line sc;
268+
F.line sc "| _ :: tl -> loop tl");
269+
F.line sc "in";
270+
F.line sc "loop assoc")
271+
272+
let gen_const_variant ?and_ { Ot.cv_name; cv_constructors } sc =
273+
F.linep sc "%s decode_pb_options_%s pb_options ="
274+
(Pb_codegen_util.let_decl_of_and and_)
275+
cv_name;
276+
277+
F.sub_scope sc (fun sc ->
278+
F.line sc "match pb_options with";
279+
List.iter
280+
(fun { Ot.cvc_name; cvc_string_value; _ } ->
281+
F.linep sc
282+
"| Ocaml_protoc_compiler_lib.Pb_option.Scalar_value \
283+
(Constant_literal \"%s\") -> (%s : %s)"
284+
cvc_string_value cvc_name cv_name)
285+
cv_constructors;
286+
F.linep sc "| _ -> Pbrt_pb_options.E.malformed_variant \"%s\"" cv_name)
287+
288+
let gen_struct ?and_ t sc =
289+
let { Ot.spec; _ } = t in
290+
let has_encoded =
291+
match spec with
292+
| Ot.Record r ->
293+
gen_record ?and_ r sc;
294+
true
295+
| Ot.Variant v ->
296+
gen_variant ?and_ v sc;
297+
true
298+
| Ot.Const_variant v ->
299+
gen_const_variant ?and_ v sc;
300+
true
301+
| Ot.Unit u ->
302+
gen_unit ?and_ u sc;
303+
true
304+
in
305+
has_encoded
306+
307+
let gen_sig ?and_ t sc =
308+
let _ = and_ in
309+
let { Ot.spec; _ } = t in
310+
311+
let f type_name =
312+
F.linep sc
313+
"val decode_pb_options_%s : Ocaml_protoc_compiler_lib.Pb_option.value -> \
314+
%s"
315+
type_name type_name;
316+
F.linep sc
317+
("(** [decode_pb_options_%s decoder] decodes a "
318+
^^ "[%s] value from [decoder] *)")
319+
type_name type_name
320+
in
321+
322+
match spec with
323+
| Ot.Record { Ot.r_name; _ } ->
324+
f r_name;
325+
true
326+
| Ot.Variant { Ot.v_name; _ } ->
327+
f v_name;
328+
true
329+
| Ot.Const_variant { Ot.cv_name; _ } ->
330+
f cv_name;
331+
true
332+
| Ot.Unit { Ot.er_name; _ } ->
333+
f er_name;
334+
true
335+
336+
let ocamldoc_title = "Pb_option.set Decoding"
337+
let requires_mutable_records = true
338+
339+
let plugin : Pb_codegen_plugin.t =
340+
let module P = struct
341+
let gen_sig = gen_sig
342+
let gen_struct = gen_struct
343+
let ocamldoc_title = ocamldoc_title
344+
let requires_mutable_records = requires_mutable_records
345+
end in
346+
(module P)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
(** Code generator to decode messages from protobuf message options *)
2+
3+
include Pb_codegen_plugin.S
4+
5+
val plugin : Pb_codegen_plugin.t

src/compilerlib/pb_typing_validation.mli

+2
Original file line numberDiff line numberDiff line change
@@ -50,3 +50,5 @@ val validate_message :
5050
Tt.type_scope ->
5151
Pt.message ->
5252
Pb_field_type.unresolved Tt.proto_type list
53+
54+
val compile_options : Pb_raw_option.set -> Pb_option.set

0 commit comments

Comments
 (0)