forked from scylladb/scylladb
-
Notifications
You must be signed in to change notification settings - Fork 0
/
idl-compiler.py
executable file
·1791 lines (1442 loc) · 63.7 KB
/
idl-compiler.py
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
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# Copyright 2016-present ScyllaDB
#
#
# SPDX-License-Identifier: AGPL-3.0-or-later
import argparse
import pyparsing as pp
from functools import reduce
import textwrap
from numbers import Number
from pprint import pformat
from copy import copy
from typing import List
import os.path
EXTENSION = '.idl.hh'
READ_BUFF = 'input_buffer'
WRITE_BUFF = 'output_buffer'
SERIALIZER = 'serialize'
DESERIALIZER = 'deserialize'
SETSIZE = 'set_size'
SIZETYPE = 'size_type'
def reindent(indent, text):
return textwrap.indent(textwrap.dedent(text), ' ' * indent)
def fprint(f, *args):
for arg in args:
f.write(arg)
def fprintln(f, *args):
for arg in args:
f.write(arg)
f.write('\n')
def print_cw(f):
fprintln(f, """
/*
* Copyright 2016-present ScyllaDB
*/
// SPDX-License-Identifier: AGPL-3.0-or-later
/*
* This is an auto-generated code, do not modify directly.
*/
#pragma once
""")
###
### AST Nodes
###
class ASTBase:
name: str
ns_context: List[str]
def __init__(self, name):
self.name = name
@staticmethod
def combine_ns(namespaces):
return "::".join(namespaces)
def ns_qualified_name(self):
return self.name if not self.ns_context \
else self.combine_ns(self.ns_context) + "::" + self.name
class Include(ASTBase):
'''AST node representing a single `include file/module`.'''
def __init__(self, name):
super().__init__(name)
sfx = '.idl.hh'
self.is_module = name.endswith(sfx)
if self.is_module:
self.module_name = self.name[0:-len(sfx)]
def __str__(self):
return f"<Include(name={self.name}, is_module={self.is_module})>"
def __repr__(self):
return self.__str__()
class BasicType(ASTBase):
'''AST node that represents terminal grammar nodes for the non-template
types, defined either inside or outside the IDL.
These can appear either in the definition of the class fields or as a part of
template types (template arguments).
Basic type nodes can also be marked as `const` when used inside a template type,
e.g. `lw_shared_ptr<const T>`. When an IDL-defined type `T` appears somewhere
with a `const` specifier, an additional `serializer<const T>` specialization
is generated for it.'''
def __init__(self, name, is_const=False):
super().__init__(name)
self.is_const = is_const
def __str__(self):
return f"<BasicType(name={self.name}, is_const={self.is_const})>"
def __repr__(self):
return self.__str__()
def to_string(self):
if self.is_const:
return 'const ' + self.name
return self.name
class TemplateType(ASTBase):
'''AST node representing template types, for example: `std::vector<T>`.
These can appear either in the definition of the class fields or as a part of
template types (template arguments).
Such types can either be defined inside or outside the IDL.'''
def __init__(self, name, template_parameters):
super().__init__(name)
# FIXME: dirty hack to translate non-type template parameters (numbers) to BasicType objects
self.template_parameters = [
t if isinstance(t, BasicType) or isinstance(t, TemplateType) else BasicType(name=str(t)) \
for t in template_parameters]
def __str__(self):
return f"<TemplateType(name={self.name}, args={pformat(self.template_parameters)}>"
def __repr__(self):
return self.__str__()
def to_string(self):
res = self.name + '<'
res += ', '.join([p.to_string() for p in self.template_parameters])
res += '>'
return res
class EnumValue(ASTBase):
'''AST node representing a single `name=value` enumerator in the enum.
Initializer part is optional, the same as in C++ enums.'''
def __init__(self, name, initializer=None):
super().__init__(name)
self.initializer = initializer
def __str__(self):
return f"<EnumValue(name={self.name}, initializer={self.initializer})>"
def __repr__(self):
return self.__str__()
class EnumDef(ASTBase):
'''AST node representing C++ `enum class` construct.
Consists of individual initializers in form of `EnumValue` objects.
Should have an underlying type explicitly specified.'''
def __init__(self, name, underlying_type, members):
super().__init__(name)
self.underlying_type = underlying_type
self.members = members
def __str__(self):
return f"<EnumDef(name={self.name}, underlying_type={self.underlying_type}, members={pformat(self.members)})>";
def __repr__(self):
return self.__str__()
def serializer_write_impl(self, cout):
name = self.ns_qualified_name()
fprintln(cout, f"""
{self.template_declaration}
template <typename Output>
void serializer<{name}>::write(Output& buf, const {name}& v) {{
serialize(buf, static_cast<{self.underlying_type}>(v));
}}""")
def serializer_read_impl(self, cout):
name = self.ns_qualified_name()
fprintln(cout, f"""
{self.template_declaration}
template<typename Input>
{name} serializer<{name}>::read(Input& buf) {{
return static_cast<{name}>(deserialize(buf, std::type_identity<{self.underlying_type}>()));
}}""")
class Attributes(ASTBase):
''' AST node for representing class and field attributes.
The following attributes are supported:
- `[[writable]]` class attribute, triggers generation of writers and views
for a class.
- `[[version id]] field attribute, marks that a field is available starting
from a specific version.'''
def __init__(self, attr_items=[]):
super().__init__('attributes')
self.attr_items = attr_items
def __str__(self):
return f"[[{', '.join([a for a in self.attr_items])}]]"
def __repr__(self):
return self.__str__()
def empty(self):
return not self.attr_items
class DataClassMember(ASTBase):
'''AST node representing a data field in a class.
Can optionally have a version attribute and a default value specified.'''
def __init__(self, type, name, attribute=None, default_value=None):
super().__init__(name)
self.type = type
self.attribute = attribute
self.default_value = default_value
def __str__(self):
return f"<DataClassMember(type={self.type}, name={self.name}, attribute={self.attribute}, default_value={self.default_value})>"
def __repr__(self):
return self.__str__()
class FunctionClassMember(ASTBase):
'''AST node representing getter function in a class definition.
Can optionally have a version attribute and a default value specified.
Getter functions should be used whenever it's needed to access private
members of a class.'''
def __init__(self, type, name, attribute=None, default_value=None):
super().__init__(name)
self.type = type
self.attribute = attribute
self.default_value = default_value
def __str__(self):
return f"<FunctionClassMember(type={self.type}, name={self.name}, attribute={self.attribute}, default_value={self.default_value})>"
def __repr__(self):
return self.__str__()
class ClassTemplateParam(ASTBase):
'''AST node representing a single template argument of a class template
definition, such as `typename T`.'''
def __init__(self, typename, name):
super().__init__(name)
self.typename = typename
def __str__(self):
return f"<ClassTemplateParam(typename={self.typename}, name={self.name})>"
def __repr__(self):
return self.__str__()
class ClassDef(ASTBase):
'''AST node representing a class definition. Can use either `class` or `struct`
keyword to define a class.
The following specifiers are allowed in a class declaration:
- `final` -- if a class is marked with this keyword it will not contain a
size argument. Final classes cannot be extended by a future version, so
it should be used with care.
- `stub` -- no code will be generated for the class, it's only there for
documentation.
Also it's possible to specify a `[[writable]]` attribute for a class, which
means that writers and views will be generated for the class.
Classes are also can be declared as template classes, much the same as in C++.
In this case the template declaration syntax mimics C++ templates.'''
def __init__(self, name, members, final, stub, attribute, template_params):
super().__init__(name)
self.members = members
self.final = final
self.stub = stub
self.attribute = attribute
self.template_params = template_params
def __str__(self):
return f"<ClassDef(name={self.name}, members={pformat(self.members)}, final={self.final}, stub={self.stub}, attribute={self.attribute}, template_params={self.template_params})>"
def __repr__(self):
return self.__str__()
def serializer_write_impl(self, cout):
name = self.ns_qualified_name()
full_name = name + self.template_param_names_str
fprintln(cout, f"""
{self.template_declaration}
template <typename Output>
void serializer<{full_name}>::write(Output& buf, const {full_name}& obj) {{""")
if not self.final:
fprintln(cout, f""" {SETSIZE}(buf, obj);""")
for member in self.members:
if isinstance(member, ClassDef) or isinstance(member, EnumDef):
continue
fprintln(cout, f""" static_assert(is_equivalent<decltype(obj.{member.name}), {param_type(member.type)}>::value, "member value has a wrong type");
{SERIALIZER}(buf, obj.{member.name});""")
fprintln(cout, "}")
def serializer_read_impl(self, cout):
name = self.ns_qualified_name()
fprintln(cout, f"""
{self.template_declaration}
template <typename Input>
{name}{self.template_param_names_str} serializer<{name}{self.template_param_names_str}>::read(Input& buf) {{
return seastar::with_serialized_stream(buf, [] (auto& buf) {{""")
if not self.members:
if not self.final:
fprintln(cout, f""" {SIZETYPE} size = {DESERIALIZER}(buf, std::type_identity<{SIZETYPE}>());
buf.skip(size - sizeof({SIZETYPE}));""")
elif not self.final:
fprintln(cout, f""" {SIZETYPE} size = {DESERIALIZER}(buf, std::type_identity<{SIZETYPE}>());
auto in = buf.read_substream(size - sizeof({SIZETYPE}));""")
else:
fprintln(cout, """ auto& in = buf;""")
params = []
local_names = {}
for index, param in enumerate(self.members):
if isinstance(param, ClassDef) or isinstance(param, EnumDef):
continue
local_param = "__local_" + str(index)
local_names[param.name] = local_param
if param.attribute:
deflt = param_type(param.type) + "()"
if param.default_value:
deflt = param.default_value
if deflt in local_names:
deflt = local_names[deflt]
fprintln(cout, f""" auto {local_param} = (in.size()>0) ?
{DESERIALIZER}(in, std::type_identity<{param_type(param.type)}>()) : {deflt};""")
else:
fprintln(cout, f""" auto {local_param} = {DESERIALIZER}(in, std::type_identity<{param_type(param.type)}>());""")
params.append("std::move(" + local_param + ")")
fprintln(cout, f"""
{name}{self.template_param_names_str} res {{{", ".join(params)}}};
return res;
}});
}}""")
def serializer_skip_impl(self, cout):
name = self.ns_qualified_name()
fprintln(cout, f"""
{self.template_declaration}
template <typename Input>
void serializer<{name}{self.template_param_names_str}>::skip(Input& buf) {{
seastar::with_serialized_stream(buf, [] (auto& buf) {{""")
if not self.final:
fprintln(cout, f""" {SIZETYPE} size = {DESERIALIZER}(buf, std::type_identity<{SIZETYPE}>());
buf.skip(size - sizeof({SIZETYPE}));""")
else:
for m in get_members(self):
full_type = param_view_type(m.type)
fprintln(cout, f" ser::skip(buf, std::type_identity<{full_type}>());")
fprintln(cout, """ });\n}""")
class RpcVerbParam(ASTBase):
"""AST element representing a single argument in an RPC verb declaration.
Consists of:
* Argument type
* Argument name (optional)
* Additional attributes (only [[version]] attribute is supported).
If the name is omitted, then this argument will have a placeholder name of form `_N`, where N is the index
of the argument in the argument list for an RPC verb.
If the [[version]] attribute is specified, then handler function signature for an RPC verb will contain this
argument as an `rpc::optional<>`.
If the [[unique_ptr]] attribute is specified then handler function signature for an RPC verb will contain this
argument as an `foreign_ptr<unique_ptr<>>`
If the [[lw_shared_ptr]] attribute is specified then handler function signature for an RPC verb will contain this
argument as an `foreign_ptr<lw_shared_ptr<>>`
If the [[ref]] attribute is specified the send function signature will contain this type as const reference"""
def __init__(self, type, name, attributes=Attributes()):
self.type = type
self.name = name
self.attributes = attributes
def __str__(self):
return f"<RpcVerbParam(type={self.type}, name={self.name}, attributes={self.attributes})>"
def __repr__(self):
return self.__str__()
def is_optional(self):
return True in [a.startswith('version') for a in self.attributes.attr_items]
def is_lw_shared(self):
return True in [a.startswith('lw_shared_ptr') for a in self.attributes.attr_items]
def is_unique(self):
return True in [a.startswith('unique_ptr') for a in self.attributes.attr_items]
def is_ref(self):
return True in [a.startswith('ref') for a in self.attributes.attr_items]
def to_string(self):
res = self.type.to_string()
if self.is_optional():
res = 'rpc::optional<' + res + '>'
if self.name:
res += ' '
res += self.name
return res
def to_string_send_fn_signature(self):
res = self.type.to_string()
if self.is_ref():
res = 'const ' + res + '&'
if self.name:
res += ' '
res += self.name
return res
def to_string_handle_ret_value(self):
res = self.type.to_string()
if self.is_unique():
res = 'foreign_ptr<std::unique_ptr<' + res + '>>'
elif self.is_lw_shared():
res = 'foreign_ptr<lw_shared_ptr<' + res + '>>'
return res
class RpcVerb(ASTBase):
"""AST element representing an RPC verb declaration.
`my_verb` RPC verb declaration corresponds to the
`netw::messaging_verb::MY_VERB` enumeration value to identify the
new RPC verb.
For a given `idl_module.idl.hh` file, a registrator class named
`idl_module_rpc_verbs` will be created if there are any RPC verbs
registered within the IDL module file.
These are the methods being created for each RPC verb:
static void register_my_verb(netw::messaging_service* ms, std::function<return_value(args...)>&&);
static future<> unregister_my_verb(netw::messaging_service* ms);
static future<> send_my_verb(netw::messaging_service* ms, netw::msg_addr id, args...);
static future<> send_my_verb(netw::messaging_service* ms, locator::host_id id, args...);
Each method accepts a pointer to an instance of messaging_service
object, which contains the underlying seastar RPC protocol
implementation, that is used to register verbs and pass messages.
There is also a method to unregister all verbs at once:
static future<> unregister(netw::messaging_service* ms);
The following attributes are supported when declaring an RPC verb
in the IDL:
- [[with_client_info]] - the handler will contain a const reference to
an `rpc::client_info` as the first argument.
- [[with_timeout]] - an additional time_point parameter is supplied
to the handler function and send* method uses send_message_*_timeout
variant of internal function to actually send the message.
Incompatible with [[cancellable]].
- [[cancellable]] - an additional abort_source& parameter is supplied
to the handler function and send* method uses send_message_*_cancellable
variant of internal function to actually send the message.
Incompatible with [[with_timeout]].
- [[one_way]] - the handler function is annotated by
future<rpc::no_wait_type> return type to designate that a client
doesn't need to wait for an answer.
The `-> return_values` clause is optional for two-way messages. If omitted,
the return type is set to be `future<>`.
For one-way verbs, the use of return clause is prohibited and the
signature of `send*` function always returns `future<>`."""
def __init__(self, name, parameters, return_values, with_client_info, with_timeout, cancellable, one_way):
super().__init__(name)
self.params = parameters
self.return_values = return_values
self.with_client_info = with_client_info
self.with_timeout = with_timeout
self.cancellable = cancellable
self.one_way = one_way
def __str__(self):
return f"<RpcVerb(name={self.name}, params={self.params}, return_values={self.return_values}, with_client_info={self.with_client_info}, with_timeout={self.with_timeout}, cancellable={self.cancellable}, one_way={self.one_way})>"
def __repr__(self):
return self.__str__()
def send_function_name(self):
send_fn = 'send_message'
if self.one_way:
send_fn += '_oneway'
if self.with_timeout:
send_fn += '_timeout'
if self.cancellable:
send_fn += '_cancellable'
return send_fn
def handler_function_return_values(self):
if self.one_way:
return 'future<rpc::no_wait_type>'
if not self.return_values:
return 'future<>'
l = len(self.return_values)
ret = 'rpc::tuple<' if l > 1 else ''
for t in self.return_values:
ret = ret + t.to_string_handle_ret_value() + ', '
ret = ret[:-2]
if l > 1:
ret = ret + '>'
return f"future<{ret}>"
def send_function_return_type(self):
if self.one_way or not self.return_values:
return 'future<>'
l = len(self.return_values)
ret = 'rpc::tuple<' if l > 1 else ''
for t in self.return_values:
ret = ret + t.to_string() + ', '
ret = ret[:-2]
if l > 1:
ret = ret + '>'
return f"future<{ret}>"
def messaging_verb_enum_case(self):
return f'netw::messaging_verb::{self.name.upper()}'
def handler_function_parameters_str(self):
res = []
if self.with_client_info:
res.append(RpcVerbParam(type=BasicType(name='rpc::client_info&', is_const=True), name='info'))
if self.with_timeout:
res.append(RpcVerbParam(type=BasicType(name='rpc::opt_time_point'), name='timeout'))
if self.params:
res.extend(self.params)
return ', '.join([p.to_string() for p in res])
def send_function_signature_params_list(self, include_placeholder_names, dst_type):
res = f'netw::messaging_service* ms, {dst_type} id'
if self.with_timeout:
res += ', netw::messaging_service::clock_type::time_point timeout'
if self.cancellable:
res += ', abort_source& as'
if self.params:
for idx, p in enumerate(self.params):
res += ', ' + p.to_string_send_fn_signature()
if include_placeholder_names and not p.name:
res += f' _{idx + 1}'
return res
def send_message_argument_list(self):
res = f'ms, {self.messaging_verb_enum_case()}, id'
if self.with_timeout:
res += ', timeout'
if self.cancellable:
res += ', as'
if self.params:
for idx, p in enumerate(self.params):
res += ', ' + f'std::move({p.name if p.name else f"_{idx + 1}"})'
return res
def send_function_invocation(self):
res = 'return ' + self.send_function_name()
if not (self.one_way):
res += '<' + self.send_function_return_type() + '>'
res += '(' + self.send_message_argument_list() + ');'
return res
class NamespaceDef(ASTBase):
'''AST node representing a namespace scope.
It has the same meaning as in C++ or other languages with similar facilities.
A namespace can contain one of the following top-level constructs:
- namespaces
- class definitions
- enum definitions'''
def __init__(self, name, members):
super().__init__(name)
self.members = members
def __str__(self):
return f"<NamespaceDef(name={self.name}, members={pformat(self.members)})>"
def __repr__(self):
return self.__str__()
###
### Parse actions, which transform raw tokens into structured representation: specialized AST nodes
###
def basic_type_parse_action(tokens):
return BasicType(name=tokens[0])
def include_parse_action(tokens):
return Include(name=tokens[0])
def template_type_parse_action(tokens):
return TemplateType(name=tokens['template_name'], template_parameters=tokens["template_parameters"].asList())
def type_parse_action(tokens):
if len(tokens) == 1:
return tokens[0]
# If we have two tokens in type parse action then
# it's because we have BasicType production with `const`
# NOTE: template types cannot have `const` modifier at the moment,
# this wouldn't parse.
tokens[1].is_const = True
return tokens[1]
def enum_value_parse_action(tokens):
initializer = None
if len(tokens) == 2:
initializer = tokens[1]
return EnumValue(name=tokens[0], initializer=initializer)
def enum_def_parse_action(tokens):
return EnumDef(name=tokens['name'], underlying_type=tokens['underlying_type'], members=tokens['enum_values'].asList())
def attributes_parse_action(tokens):
items = []
for attr_clause in tokens:
# Split individual attributes inside each attribute clause by commas and strip extra whitespace characters
items += [arg.strip() for arg in attr_clause.split(',')]
return Attributes(attr_items=items)
def class_member_parse_action(tokens):
member_name = tokens['name']
raw_attrs = tokens['attributes']
attribute = raw_attrs.attr_items[0] if not raw_attrs.empty() else None
default = tokens['default'][0] if 'default' in tokens else None
if not isinstance(member_name, str): # accessor function declaration
return FunctionClassMember(type=tokens["type"], name=member_name[0], attribute=attribute, default_value=default)
# data member
return DataClassMember(type=tokens["type"], name=member_name, attribute=attribute, default_value=default)
def class_def_parse_action(tokens):
is_final = 'final' in tokens
is_stub = 'stub' in tokens
class_members = tokens['members'].asList() if 'members' in tokens else []
raw_attrs = tokens['attributes']
attribute = raw_attrs.attr_items[0] if not raw_attrs.empty() else None
template_params = None
if 'template' in tokens:
template_params = [ClassTemplateParam(typename=tp[0], name=tp[1]) for tp in tokens['template']]
return ClassDef(name=tokens['name'], members=class_members, final=is_final, stub=is_stub, attribute=attribute, template_params=template_params)
def rpc_verb_param_parse_action(tokens):
type = tokens['type']
name = tokens['ident'] if 'ident' in tokens else None
attrs = tokens['attrs']
return RpcVerbParam(type=type, name=name, attributes=attrs)
def rpc_verb_return_val_parse_action(tokens):
type = tokens['type']
attrs = tokens['attrs']
return RpcVerbParam(type=type, name='', attributes=attrs)
def rpc_verb_parse_action(tokens):
name = tokens['name']
raw_attrs = tokens['attributes']
params = tokens['params'] if 'params' in tokens else []
with_timeout = not raw_attrs.empty() and 'with_timeout' in raw_attrs.attr_items
cancellable = not raw_attrs.empty() and 'cancellable' in raw_attrs.attr_items
with_client_info = not raw_attrs.empty() and 'with_client_info' in raw_attrs.attr_items
one_way = not raw_attrs.empty() and 'one_way' in raw_attrs.attr_items
if one_way and 'return_values' in tokens:
raise Exception(f"Invalid return type specification for one-way RPC verb '{name}'")
if with_timeout and cancellable:
raise Exception(f"Error in verb {name}: [[with_timeout]] cannot be used together with [[cancellable]] in the same verb")
return RpcVerb(name=name, parameters=params, return_values=tokens.get('return_values'), with_client_info=with_client_info, with_timeout=with_timeout, cancellable=cancellable, one_way=one_way)
def namespace_parse_action(tokens):
return NamespaceDef(name=tokens['name'], members=tokens['ns_members'].asList())
def parse_file(file_name):
'''Parse the input from the file using IDL grammar syntax and generate AST'''
number = pp.pyparsing_common.signed_integer
identifier = pp.pyparsing_common.identifier
include_kw = pp.Keyword('#include').suppress()
lbrace = pp.Literal('{').suppress()
rbrace = pp.Literal('}').suppress()
cls = pp.Keyword('class').suppress()
colon = pp.Literal(":").suppress()
semi = pp.Literal(";").suppress()
langle = pp.Literal("<").suppress()
rangle = pp.Literal(">").suppress()
equals = pp.Literal("=").suppress()
comma = pp.Literal(",").suppress()
lparen = pp.Literal("(")
rparen = pp.Literal(")")
lbrack = pp.Literal("[").suppress()
rbrack = pp.Literal("]").suppress()
struct = pp.Keyword('struct').suppress()
template = pp.Keyword('template').suppress()
final = pp.Keyword('final')
stub = pp.Keyword('stub')
const = pp.Keyword('const')
dcolon = pp.Literal("::")
ns_qualified_ident = pp.Combine(pp.Optional(dcolon) + pp.delimitedList(identifier, "::", combine=True))
enum_lit = pp.Keyword('enum').suppress()
ns = pp.Keyword("namespace").suppress()
verb = pp.Keyword("verb").suppress()
btype = ns_qualified_ident.copy()
btype.setParseAction(basic_type_parse_action)
type = pp.Forward()
tmpl = ns_qualified_ident("template_name") + langle + pp.Group(pp.delimitedList(type | number))("template_parameters") + rangle
tmpl.setParseAction(template_type_parse_action)
type <<= tmpl | (pp.Optional(const) + btype)
type.setParseAction(type_parse_action)
include_stmt = include_kw - pp.QuotedString('"')
include_stmt.setParseAction(include_parse_action)
enum_class = enum_lit - cls
enum_init = equals - number
enum_value = identifier - pp.Optional(enum_init)
enum_value.setParseAction(enum_value_parse_action)
enum_values = lbrace - pp.delimitedList(enum_value) - pp.Optional(comma) - rbrace
enum = enum_class - identifier("name") - colon - identifier("underlying_type") - enum_values("enum_values") + pp.Optional(semi)
enum.setParseAction(enum_def_parse_action)
content = pp.Forward()
attrib = lbrack - lbrack - pp.SkipTo(']') - rbrack - rbrack
opt_attributes = pp.ZeroOrMore(attrib)("attributes")
opt_attributes.setParseAction(attributes_parse_action)
default_value = equals - pp.SkipTo(';')
member_name = pp.Combine(identifier - pp.Optional(lparen - rparen)("function_marker"))
class_member = type("type") - member_name("name") - opt_attributes - pp.Optional(default_value)("default") - semi
class_member.setParseAction(class_member_parse_action)
template_param = pp.Group(identifier("type") - identifier("name"))
template_def = template - langle - pp.delimitedList(template_param)("params") - rangle
class_content = pp.Forward()
class_def = pp.Optional(template_def)("template") + (cls | struct) - ns_qualified_ident("name") - \
pp.Optional(final)("final") - pp.Optional(stub)("stub") - opt_attributes - \
lbrace - pp.ZeroOrMore(class_content)("members") - rbrace - pp.Optional(semi)
class_content <<= enum | class_def | class_member
class_def.setParseAction(class_def_parse_action)
rpc_verb_param = type("type") - pp.Optional(identifier)("ident") - opt_attributes("attrs")
rpc_verb_param.setParseAction(rpc_verb_param_parse_action)
rpc_verb_params = pp.delimitedList(rpc_verb_param)
rpc_verb_return_val = type("type") - opt_attributes("attrs")
rpc_verb_return_val.setParseAction(rpc_verb_return_val_parse_action)
rpc_verb_return_vals = pp.delimitedList(rpc_verb_return_val)
rpc_verb = verb - opt_attributes - identifier("name") - \
lparen.suppress() - pp.Optional(rpc_verb_params("params")) - rparen.suppress() - \
pp.Optional(pp.Literal("->").suppress() - rpc_verb_return_vals("return_values")) - pp.Optional(semi)
rpc_verb.setParseAction(rpc_verb_parse_action)
namespace = ns - identifier("name") - lbrace - pp.OneOrMore(content)("ns_members") - rbrace
namespace.setParseAction(namespace_parse_action)
content <<= include_stmt | enum | class_def | rpc_verb | namespace
for varname in ("include_stmt", "enum", "class_def", "class_member", "content", "namespace", "template_def"):
locals()[varname].setName(varname)
rt = pp.OneOrMore(content)
rt.ignore(pp.cppStyleComment)
return rt.parseFile(file_name, parseAll=True)
def declare_methods(hout, name, template_param=""):
fprintln(hout, f"""
template <{template_param}>
struct serializer<{name}> {{
template <typename Output>
static void write(Output& buf, const {name}& v);
template <typename Input>
static {name} read(Input& buf);
template <typename Input>
static void skip(Input& buf);
}};
""")
fprintln(hout, f"""
template <{template_param}>
struct serializer<const {name}> : public serializer<{name}>
{{}};
""")
def template_params_str(template_params):
if not template_params:
return ""
return ", ".join(map(lambda param: param.typename + " " + param.name, template_params))
def handle_enum(enum, hout, cout):
'''Generate serializer declarations and definitions for an IDL enum'''
temp_def = template_params_str(enum.parent_template_params)
name = enum.ns_qualified_name()
declare_methods(hout, name, temp_def)
enum.serializer_write_impl(cout)
enum.serializer_read_impl(cout)
def join_template(template_params):
return "<" + ", ".join([param_type(p) for p in template_params]) + ">"
def param_type(t):
if isinstance(t, BasicType):
return 'const ' + t.name if t.is_const else t.name
elif isinstance(t, TemplateType):
return t.name + join_template(t.template_parameters)
def flat_type(t):
if isinstance(t, BasicType):
return t.name
elif isinstance(t, TemplateType):
return (t.name + "__" + "_".join([flat_type(p) for p in t.template_parameters])).replace('::', '__')
local_types = {}
local_writable_types = {}
rpc_verbs = {}
def resolve_basic_type_ref(type: BasicType):
if type.name not in local_types:
raise KeyError(f"Failed to resolve type reference for '{type.name}'")
return local_types[type.name]
def list_types(t):
if isinstance(t, BasicType):
return [t.name]
elif isinstance(t, TemplateType):
return reduce(lambda a, b: a + b, [list_types(p) for p in t.template_parameters])
def list_local_writable_types(t):
return {l for l in list_types(t) if l in local_writable_types}
def is_basic_type(t):
return isinstance(t, BasicType) and t.name not in local_writable_types
def is_local_writable_type(t):
if isinstance(t, str): # e.g. `t` is a local class name
return t in local_writable_types
return t.name in local_writable_types
def get_template_name(lst):
return lst["template_name"] if not isinstance(lst, str) and len(lst) > 1 else None
def is_vector(t):
return isinstance(t, TemplateType) and (t.name == "std::vector" or t.name == "utils::chunked_vector")
def is_variant(t):
return isinstance(t, TemplateType) and (t.name == "boost::variant" or t.name == "std::variant")
def is_optional(t):
return isinstance(t, TemplateType) and t.name == "std::optional"
created_writers = set()
def get_member_name(name):
return name if not name.endswith('()') else name[:-2]
def get_members(cls):
return [p for p in cls.members if not isinstance(p, ClassDef) and not isinstance(p, EnumDef)]
def get_variant_type(t):
if is_variant(t):
return "variant"
return param_type(t)
def variant_to_member(template_parameters):
return [DataClassMember(name=get_variant_type(x), type=x) for x in template_parameters if is_local_writable_type(x) or is_variant(x)]
def variant_info(cls, template_parameters):
variant_info_cls = copy(cls) # shallow copy of cls
variant_info_cls.members = variant_to_member(template_parameters)
return variant_info_cls
stubs = set()
def is_stub(cls):
return cls in stubs
def handle_visitors_state(cls, cout, classes=[]):
name = "__".join(classes) if classes else cls.name
frame = "empty_frame" if cls.final else "frame"
fprintln(cout, f"""
template<typename Output>
struct state_of_{name} {{
{frame}<Output> f;""")
if classes:
local_state = "state_of_" + "__".join(classes[:-1]) + '<Output>'
fprintln(cout, f" {local_state} _parent;")
if cls.final:
fprintln(cout, f" state_of_{name}({local_state} parent) : _parent(parent) {{}}")
fprintln(cout, "};")
members = get_members(cls)
member_class = classes if classes else [cls.name]
for m in members:
if is_local_writable_type(m.type):
handle_visitors_state(local_writable_types[param_type(m.type)], cout, member_class + [m.name])
if is_variant(m.type):
handle_visitors_state(variant_info(cls, m.type.template_parameters), cout, member_class + [m.name])
def get_dependency(cls):
members = get_members(cls)
return reduce(lambda a, b: a | b, [list_local_writable_types(m.type) for m in members], set())
def optional_add_methods(typ):
res = reindent(4, """
void skip() {
serialize(_out, false);
}""")
if is_basic_type(typ):
added_type = typ
elif is_local_writable_type(typ):
added_type = param_type(typ) + "_view"
else:
print("non supported optional type ", typ)
raise "non supported optional type " + param_type(typ)
res = res + reindent(4, f"""
void write(const {added_type}& obj) {{
serialize(_out, true);
serialize(_out, obj);
}}""")
if is_local_writable_type(typ):
res = res + reindent(4, f"""
writer_of_{param_type(typ)}<Output> write() {{
serialize(_out, true);
return {{_out}};
}}""")
return res
def vector_add_method(current, base_state):
typ = current.type
res = ""
if is_basic_type(typ.template_parameters[0]):