03362b0e6565161be9898828982e395d83a8fd6c
[vpp.git] / src / tools / vppapigen / vppapigen.py
1 #!/usr/bin/env python3
2
3 import ply.lex as lex
4 import ply.yacc as yacc
5 import sys
6 import argparse
7 import keyword
8 import logging
9 import binascii
10 import os
11 import sys
12
13 log = logging.getLogger('vppapigen')
14
15 # Ensure we don't leave temporary files around
16 sys.dont_write_bytecode = True
17
18 #
19 # VPP API language
20 #
21
22 # Global dictionary of new types (including enums)
23 global_types = {}
24
25 seen_imports = {}
26
27
28 def global_type_add(name, obj):
29     '''Add new type to the dictionary of types '''
30     type_name = 'vl_api_' + name + '_t'
31     if type_name in global_types:
32         raise KeyError("Attempted redefinition of {!r} with {!r}.".format(
33             name, obj))
34     global_types[type_name] = obj
35
36
37 # All your trace are belong to us!
38 def exception_handler(exception_type, exception, traceback):
39     print("%s: %s" % (exception_type.__name__, exception))
40
41
42 #
43 # Lexer
44 #
45 class VPPAPILexer(object):
46     def __init__(self, filename):
47         self.filename = filename
48
49     reserved = {
50         'service': 'SERVICE',
51         'rpc': 'RPC',
52         'returns': 'RETURNS',
53         'null': 'NULL',
54         'stream': 'STREAM',
55         'events': 'EVENTS',
56         'define': 'DEFINE',
57         'typedef': 'TYPEDEF',
58         'enum': 'ENUM',
59         'typeonly': 'TYPEONLY',
60         'manual_print': 'MANUAL_PRINT',
61         'manual_endian': 'MANUAL_ENDIAN',
62         'dont_trace': 'DONT_TRACE',
63         'autoreply': 'AUTOREPLY',
64         'option': 'OPTION',
65         'u8': 'U8',
66         'u16': 'U16',
67         'u32': 'U32',
68         'u64': 'U64',
69         'i8': 'I8',
70         'i16': 'I16',
71         'i32': 'I32',
72         'i64': 'I64',
73         'f64': 'F64',
74         'bool': 'BOOL',
75         'string': 'STRING',
76         'import': 'IMPORT',
77         'true': 'TRUE',
78         'false': 'FALSE',
79         'union': 'UNION',
80     }
81
82     tokens = ['STRING_LITERAL',
83               'ID', 'NUM'] + list(reserved.values())
84
85     t_ignore_LINE_COMMENT = '//.*'
86
87     def t_FALSE(self, t):
88         r'false'
89         t.value = False
90         return t
91
92     def t_TRUE(self, t):
93         r'false'
94         t.value = True
95         return t
96
97     def t_NUM(self, t):
98         r'0[xX][0-9a-fA-F]+|-?\d+\.?\d*'
99         base = 16 if t.value.startswith('0x') else 10
100         if '.' in t.value:
101             t.value = float(t.value)
102         else:
103             t.value = int(t.value, base)
104         return t
105
106     def t_ID(self, t):
107         r'[a-zA-Z_][a-zA-Z_0-9]*'
108         # Check for reserved words
109         t.type = VPPAPILexer.reserved.get(t.value, 'ID')
110         return t
111
112     # C string
113     def t_STRING_LITERAL(self, t):
114         r'\"([^\\\n]|(\\.))*?\"'
115         t.value = str(t.value).replace("\"", "")
116         return t
117
118     # C or C++ comment (ignore)
119     def t_comment(self, t):
120         r'(/\*(.|\n)*?\*/)|(//.*)'
121         t.lexer.lineno += t.value.count('\n')
122
123     # Error handling rule
124     def t_error(self, t):
125         raise ParseError("Illegal character '{}' ({})"
126                          "in {}: line {}".format(t.value[0],
127                                                  hex(ord(t.value[0])),
128                                                  self.filename,
129                                                  t.lexer.lineno))
130         t.lexer.skip(1)
131
132     # Define a rule so we can track line numbers
133     def t_newline(self, t):
134         r'\n+'
135         t.lexer.lineno += len(t.value)
136
137     literals = ":{}[];=.,"
138
139     # A string containing ignored characters (spaces and tabs)
140     t_ignore = ' \t'
141
142
143 def crc_block_combine(block, crc):
144     s = str(block).encode()
145     return binascii.crc32(s, crc) & 0xffffffff
146
147
148 def vla_is_last_check(name, block):
149     vla = False
150     for i, b in enumerate(block):
151         if isinstance(b, Array) and b.vla:
152             vla = True
153             if i + 1 < len(block):
154                 raise ValueError(
155                     'VLA field "{}" must be the last field in message "{}"'
156                     .format(b.fieldname, name))
157         elif b.fieldtype.startswith('vl_api_'):
158             if global_types[b.fieldtype].vla:
159                 vla = True
160                 if i + 1 < len(block):
161                     raise ValueError(
162                         'VLA field "{}" must be the last '
163                         'field in message "{}"'
164                         .format(b.fieldname, name))
165         elif b.fieldtype == 'string' and b.length == 0:
166             vla = True
167             if i + 1 < len(block):
168                 raise ValueError(
169                     'VLA field "{}" must be the last '
170                     'field in message "{}"'
171                     .format(b.fieldname, name))
172     return vla
173
174
175 class Service():
176     def __init__(self, caller, reply, events=None, stream=False):
177         self.caller = caller
178         self.reply = reply
179         self.stream = stream
180         self.events = [] if events is None else events
181
182
183 class Typedef():
184     def __init__(self, name, flags, block):
185         self.name = name
186         self.flags = flags
187         self.block = block
188         self.crc = str(block).encode()
189         self.manual_print = False
190         self.manual_endian = False
191         for f in flags:
192             if f == 'manual_print':
193                 self.manual_print = True
194             elif f == 'manual_endian':
195                 self.manual_endian = True
196
197         global_type_add(name, self)
198
199         self.vla = vla_is_last_check(name, block)
200
201     def __repr__(self):
202         return self.name + str(self.flags) + str(self.block)
203
204
205 class Using():
206     def __init__(self, name, flags, alias):
207         self.name = name
208         self.vla = False
209
210         self.manual_print = False
211         self.manual_endian = False
212         for f in flags:
213             if f == 'manual_print':
214                 self.manual_print = True
215             elif f == 'manual_endian':
216                 self.manual_endian = True
217
218         if isinstance(alias, Array):
219             a = {'type': alias.fieldtype,
220                  'length': alias.length}
221         else:
222             a = {'type': alias.fieldtype}
223         self.alias = a
224         self.crc = str(alias).encode()
225         global_type_add(name, self)
226
227     def __repr__(self):
228         return self.name + str(self.alias)
229
230
231 class Union():
232     def __init__(self, name, flags, block):
233         self.type = 'Union'
234         self.manual_print = False
235         self.manual_endian = False
236         self.name = name
237
238         for f in flags:
239             if f == 'manual_print':
240                 self.manual_print = True
241             elif f == 'manual_endian':
242                 self.manual_endian = True
243
244         self.block = block
245         self.crc = str(block).encode()
246         self.vla = vla_is_last_check(name, block)
247
248         global_type_add(name, self)
249
250     def __repr__(self):
251         return str(self.block)
252
253
254 class Define():
255     def __init__(self, name, flags, block):
256         self.name = name
257         self.flags = flags
258         self.block = block
259         self.crc = str(block).encode()
260         self.dont_trace = False
261         self.manual_print = False
262         self.manual_endian = False
263         self.autoreply = False
264         self.singular = False
265         for f in flags:
266             if f == 'dont_trace':
267                 self.dont_trace = True
268             elif f == 'manual_print':
269                 self.manual_print = True
270             elif f == 'manual_endian':
271                 self.manual_endian = True
272             elif f == 'autoreply':
273                 self.autoreply = True
274
275         for b in block:
276             if isinstance(b, Option):
277                 if b[1] == 'singular' and b[2] == 'true':
278                     self.singular = True
279                 block.remove(b)
280         self.vla = vla_is_last_check(name, block)
281
282     def __repr__(self):
283         return self.name + str(self.flags) + str(self.block)
284
285
286 class Enum():
287     def __init__(self, name, block, enumtype='u32'):
288         self.name = name
289         self.enumtype = enumtype
290         self.vla = False
291
292         count = 0
293         for i, b in enumerate(block):
294             if type(b) is list:
295                 count = b[1]
296             else:
297                 count += 1
298                 block[i] = [b, count]
299
300         self.block = block
301         self.crc = str(block).encode()
302         global_type_add(name, self)
303
304     def __repr__(self):
305         return self.name + str(self.block)
306
307
308 class Import():
309
310     def __new__(cls, *args, **kwargs):
311         if args[0] not in seen_imports:
312             instance = super().__new__(cls)
313             instance._initialized = False
314             seen_imports[args[0]] = instance
315
316         return seen_imports[args[0]]
317
318     def __init__(self, filename):
319         if self._initialized:
320             return
321         else:
322             self.filename = filename
323             # Deal with imports
324             parser = VPPAPI(filename=filename)
325             dirlist = dirlist_get()
326             f = filename
327             for dir in dirlist:
328                 f = os.path.join(dir, filename)
329                 if os.path.exists(f):
330                     break
331             if sys.version[0] == '2':
332                 with open(f) as fd:
333                     self.result = parser.parse_file(fd, None)
334             else:
335                 with open(f, encoding='utf-8') as fd:
336                     self.result = parser.parse_file(fd, None)
337             self._initialized = True
338
339     def __repr__(self):
340         return self.filename
341
342
343 class Option():
344     def __init__(self, option, value):
345         self.type = 'Option'
346         self.option = option
347         self.value = value
348         self.crc = str(option).encode()
349
350     def __repr__(self):
351         return str(self.option)
352
353     def __getitem__(self, index):
354         return self.option[index]
355
356
357 class Array():
358     def __init__(self, fieldtype, name, length, modern_vla=False):
359         self.type = 'Array'
360         self.fieldtype = fieldtype
361         self.fieldname = name
362         self.modern_vla = modern_vla
363         if type(length) is str:
364             self.lengthfield = length
365             self.length = 0
366             self.vla = True
367         else:
368             self.length = length
369             self.lengthfield = None
370             self.vla = False
371
372     def __repr__(self):
373         return str([self.fieldtype, self.fieldname, self.length,
374                     self.lengthfield])
375
376
377 class Field():
378     def __init__(self, fieldtype, name, limit=None):
379         self.type = 'Field'
380         self.fieldtype = fieldtype
381
382         if self.fieldtype == 'string':
383             raise ValueError("The string type {!r} is an "
384                              "array type ".format(name))
385
386         if name in keyword.kwlist:
387             raise ValueError("Fieldname {!r} is a python keyword and is not "
388                              "accessible via the python API. ".format(name))
389         self.fieldname = name
390         self.limit = limit
391
392     def __repr__(self):
393         return str([self.fieldtype, self.fieldname])
394
395
396 class Coord(object):
397     """ Coordinates of a syntactic element. Consists of:
398             - File name
399             - Line number
400             - (optional) column number, for the Lexer
401     """
402     __slots__ = ('file', 'line', 'column', '__weakref__')
403
404     def __init__(self, file, line, column=None):
405         self.file = file
406         self.line = line
407         self.column = column
408
409     def __str__(self):
410         str = "%s:%s" % (self.file, self.line)
411         if self.column:
412             str += ":%s" % self.column
413         return str
414
415
416 class ParseError(Exception):
417     pass
418
419
420 #
421 # Grammar rules
422 #
423 class VPPAPIParser(object):
424     tokens = VPPAPILexer.tokens
425
426     def __init__(self, filename, logger):
427         self.filename = filename
428         self.logger = logger
429         self.fields = []
430
431     def _parse_error(self, msg, coord):
432         raise ParseError("%s: %s" % (coord, msg))
433
434     def _parse_warning(self, msg, coord):
435         if self.logger:
436             self.logger.warning("%s: %s" % (coord, msg))
437
438     def _coord(self, lineno, column=None):
439         return Coord(
440                 file=self.filename,
441                 line=lineno, column=column)
442
443     def _token_coord(self, p, token_idx):
444         """ Returns the coordinates for the YaccProduction object 'p' indexed
445             with 'token_idx'. The coordinate includes the 'lineno' and
446             'column'. Both follow the lex semantic, starting from 1.
447         """
448         last_cr = p.lexer.lexdata.rfind('\n', 0, p.lexpos(token_idx))
449         if last_cr < 0:
450             last_cr = -1
451         column = (p.lexpos(token_idx) - (last_cr))
452         return self._coord(p.lineno(token_idx), column)
453
454     def p_slist(self, p):
455         '''slist : stmt
456                  | slist stmt'''
457         if len(p) == 2:
458             p[0] = [p[1]]
459         else:
460             p[0] = p[1] + [p[2]]
461
462     def p_stmt(self, p):
463         '''stmt : define
464                 | typedef
465                 | option
466                 | import
467                 | enum
468                 | union
469                 | service'''
470         p[0] = p[1]
471
472     def p_import(self, p):
473         '''import : IMPORT STRING_LITERAL ';' '''
474         p[0] = Import(p[2])
475
476     def p_service(self, p):
477         '''service : SERVICE '{' service_statements '}' ';' '''
478         p[0] = p[3]
479
480     def p_service_statements(self, p):
481         '''service_statements : service_statement
482                         | service_statements service_statement'''
483         if len(p) == 2:
484             p[0] = [p[1]]
485         else:
486             p[0] = p[1] + [p[2]]
487
488     def p_service_statement(self, p):
489         '''service_statement : RPC ID RETURNS NULL ';'
490                              | RPC ID RETURNS ID ';'
491                              | RPC ID RETURNS STREAM ID ';'
492                              | RPC ID RETURNS ID EVENTS event_list ';' '''
493         if p[2] == p[4]:
494             # Verify that caller and reply differ
495             self._parse_error(
496                 'Reply ID ({}) should not be equal to Caller ID'.format(p[2]),
497                 self._token_coord(p, 1))
498         if len(p) == 8:
499             p[0] = Service(p[2], p[4], p[6])
500         elif len(p) == 7:
501             p[0] = Service(p[2], p[5], stream=True)
502         else:
503             p[0] = Service(p[2], p[4])
504
505     def p_event_list(self, p):
506         '''event_list : events
507                       | event_list events '''
508         if len(p) == 2:
509             p[0] = [p[1]]
510         else:
511             p[0] = p[1] + [p[2]]
512
513     def p_event(self, p):
514         '''events : ID
515                   | ID ',' '''
516         p[0] = p[1]
517
518     def p_enum(self, p):
519         '''enum : ENUM ID '{' enum_statements '}' ';' '''
520         p[0] = Enum(p[2], p[4])
521
522     def p_enum_type(self, p):
523         ''' enum : ENUM ID ':' enum_size '{' enum_statements '}' ';' '''
524         if len(p) == 9:
525             p[0] = Enum(p[2], p[6], enumtype=p[4])
526         else:
527             p[0] = Enum(p[2], p[4])
528
529     def p_enum_size(self, p):
530         ''' enum_size : U8
531                       | U16
532                       | U32 '''
533         p[0] = p[1]
534
535     def p_define(self, p):
536         '''define : DEFINE ID '{' block_statements_opt '}' ';' '''
537         self.fields = []
538         p[0] = Define(p[2], [], p[4])
539
540     def p_define_flist(self, p):
541         '''define : flist DEFINE ID '{' block_statements_opt '}' ';' '''
542         # Legacy typedef
543         if 'typeonly' in p[1]:
544             self._parse_error('legacy typedef. use typedef: {} {}[{}];'
545                               .format(p[1], p[2], p[4]),
546                               self._token_coord(p, 1))
547         else:
548             p[0] = Define(p[3], p[1], p[5])
549
550     def p_flist(self, p):
551         '''flist : flag
552                  | flist flag'''
553         if len(p) == 2:
554             p[0] = [p[1]]
555         else:
556             p[0] = p[1] + [p[2]]
557
558     def p_flag(self, p):
559         '''flag : MANUAL_PRINT
560                 | MANUAL_ENDIAN
561                 | DONT_TRACE
562                 | TYPEONLY
563                 | AUTOREPLY'''
564         if len(p) == 1:
565             return
566         p[0] = p[1]
567
568     def p_typedef(self, p):
569         '''typedef : TYPEDEF ID '{' block_statements_opt '}' ';' '''
570         p[0] = Typedef(p[2], [], p[4])
571
572     def p_typedef_flist(self, p):
573         '''typedef : flist TYPEDEF ID '{' block_statements_opt '}' ';' '''
574         p[0] = Typedef(p[3], p[1], p[5])
575
576     def p_typedef_alias(self, p):
577         '''typedef : TYPEDEF declaration '''
578         p[0] = Using(p[2].fieldname, [], p[2])
579
580     def p_typedef_alias_flist(self, p):
581         '''typedef : flist TYPEDEF declaration '''
582         p[0] = Using(p[3].fieldname, p[1], p[3])
583
584     def p_block_statements_opt(self, p):
585         '''block_statements_opt : block_statements '''
586         p[0] = p[1]
587
588     def p_block_statements(self, p):
589         '''block_statements : block_statement
590                             | block_statements block_statement'''
591         if len(p) == 2:
592             p[0] = [p[1]]
593         else:
594             p[0] = p[1] + [p[2]]
595
596     def p_block_statement(self, p):
597         '''block_statement : declaration
598                            | option '''
599         p[0] = p[1]
600
601     def p_enum_statements(self, p):
602         '''enum_statements : enum_statement
603                            | enum_statements enum_statement'''
604         if len(p) == 2:
605             p[0] = [p[1]]
606         else:
607             p[0] = p[1] + [p[2]]
608
609     def p_enum_statement(self, p):
610         '''enum_statement : ID '=' NUM ','
611                           | ID ',' '''
612         if len(p) == 5:
613             p[0] = [p[1], p[3]]
614         else:
615             p[0] = p[1]
616
617     def p_field_options(self, p):
618         '''field_options : field_option
619                            | field_options field_option'''
620         if len(p) == 2:
621             p[0] = p[1]
622         else:
623             p[0] = {**p[1], **p[2]}
624
625     def p_field_option(self, p):
626         '''field_option : ID
627                         | ID '=' assignee ','
628                         | ID '=' assignee
629
630         '''
631         if len(p) == 2:
632             p[0] = {p[1]: None}
633         else:
634             p[0] = {p[1]: p[3]}
635
636     def p_declaration(self, p):
637         '''declaration : type_specifier ID ';'
638                        | type_specifier ID '[' field_options ']' ';' '''
639         if len(p) == 7:
640             p[0] = Field(p[1], p[2], p[4])
641         elif len(p) == 4:
642             p[0] = Field(p[1], p[2])
643         else:
644             self._parse_error('ERROR')
645         self.fields.append(p[2])
646
647     def p_declaration_array_vla(self, p):
648         '''declaration : type_specifier ID '[' ']' ';' '''
649         p[0] = Array(p[1], p[2], 0, modern_vla=True)
650
651     def p_declaration_array(self, p):
652         '''declaration : type_specifier ID '[' NUM ']' ';'
653                        | type_specifier ID '[' ID ']' ';' '''
654
655         if len(p) != 7:
656             return self._parse_error(
657                 'array: %s' % p.value,
658                 self._coord(lineno=p.lineno))
659
660         # Make this error later
661         if type(p[4]) is int and p[4] == 0:
662             # XXX: Line number is wrong
663             self._parse_warning('Old Style VLA: {} {}[{}];'
664                                 .format(p[1], p[2], p[4]),
665                                 self._token_coord(p, 1))
666
667         if type(p[4]) is str and p[4] not in self.fields:
668             # Verify that length field exists
669             self._parse_error('Missing length field: {} {}[{}];'
670                               .format(p[1], p[2], p[4]),
671                               self._token_coord(p, 1))
672         p[0] = Array(p[1], p[2], p[4])
673
674     def p_option(self, p):
675         '''option : OPTION ID '=' assignee ';' '''
676         p[0] = Option(p[2], p[4])
677
678     def p_assignee(self, p):
679         '''assignee : NUM
680                     | TRUE
681                     | FALSE
682                     | STRING_LITERAL '''
683         p[0] = p[1]
684
685     def p_type_specifier(self, p):
686         '''type_specifier : U8
687                           | U16
688                           | U32
689                           | U64
690                           | I8
691                           | I16
692                           | I32
693                           | I64
694                           | F64
695                           | BOOL
696                           | STRING'''
697         p[0] = p[1]
698
699     # Do a second pass later to verify that user defined types are defined
700     def p_typedef_specifier(self, p):
701         '''type_specifier : ID '''
702         if p[1] not in global_types:
703             self._parse_error('Undefined type: {}'.format(p[1]),
704                               self._token_coord(p, 1))
705         p[0] = p[1]
706
707     def p_union(self, p):
708         '''union : UNION ID '{' block_statements_opt '}' ';' '''
709         p[0] = Union(p[2], [], p[4])
710
711     def p_union_flist(self, p):
712         '''union : flist UNION ID '{' block_statements_opt '}' ';' '''
713         p[0] = Union(p[3], p[1], p[5])
714
715     # Error rule for syntax errors
716     def p_error(self, p):
717         if p:
718             self._parse_error(
719                 'before: %s' % p.value,
720                 self._coord(lineno=p.lineno))
721         else:
722             self._parse_error('At end of input', self.filename)
723
724
725 class VPPAPI(object):
726
727     def __init__(self, debug=False, filename='', logger=None):
728         self.lexer = lex.lex(module=VPPAPILexer(filename), debug=debug)
729         self.parser = yacc.yacc(module=VPPAPIParser(filename, logger),
730                                 write_tables=False, debug=debug)
731         self.logger = logger
732
733     def parse_string(self, code, debug=0, lineno=1):
734         self.lexer.lineno = lineno
735         return self.parser.parse(code, lexer=self.lexer, debug=debug)
736
737     def parse_file(self, fd, debug=0):
738         data = fd.read()
739         return self.parse_string(data, debug=debug)
740
741     def autoreply_block(self, name):
742         block = [Field('u32', 'context'),
743                  Field('i32', 'retval')]
744         return Define(name + '_reply', [], block)
745
746     def process(self, objs):
747         s = {}
748         s['Option'] = {}
749         s['Define'] = []
750         s['Service'] = []
751         s['types'] = []
752         s['Import'] = []
753         s['Alias'] = {}
754         crc = 0
755         for o in objs:
756             tname = o.__class__.__name__
757             try:
758                 crc = binascii.crc32(o.crc, crc)
759             except AttributeError:
760                 pass
761             if isinstance(o, Define):
762                 s[tname].append(o)
763                 if o.autoreply:
764                     s[tname].append(self.autoreply_block(o.name))
765             elif isinstance(o, Option):
766                 s[tname][o[1]] = o[2]
767             elif type(o) is list:
768                 for o2 in o:
769                     if isinstance(o2, Service):
770                         s['Service'].append(o2)
771             elif (isinstance(o, Enum) or
772                   isinstance(o, Typedef) or
773                   isinstance(o, Union)):
774                 s['types'].append(o)
775             elif isinstance(o, Using):
776                 s['Alias'][o.name] = o
777             else:
778                 if tname not in s:
779                     raise ValueError('Unknown class type: {} {}'
780                                      .format(tname, o))
781                 s[tname].append(o)
782
783         msgs = {d.name: d for d in s['Define']}
784         svcs = {s.caller: s for s in s['Service']}
785         replies = {s.reply: s for s in s['Service']}
786         seen_services = {}
787
788         s['file_crc'] = crc
789
790         for service in svcs:
791             if service not in msgs:
792                 raise ValueError(
793                     'Service definition refers to unknown message'
794                     ' definition: {}'.format(service))
795             if svcs[service].reply != 'null' and \
796                svcs[service].reply not in msgs:
797                 raise ValueError('Service definition refers to unknown message'
798                                  ' definition in reply: {}'
799                                  .format(svcs[service].reply))
800             if service in replies:
801                 raise ValueError('Service definition refers to message'
802                                  ' marked as reply: {}'.format(service))
803             for event in svcs[service].events:
804                 if event not in msgs:
805                     raise ValueError('Service definition refers to unknown '
806                                      'event: {} in message: {}'
807                                      .format(event, service))
808                 seen_services[event] = True
809
810         # Create services implicitly
811         for d in msgs:
812             if d in seen_services:
813                 continue
814             if msgs[d].singular is True:
815                 continue
816             if d.endswith('_reply'):
817                 if d[:-6] in svcs:
818                     continue
819                 if d[:-6] not in msgs:
820                     raise ValueError('{} missing calling message'
821                                      .format(d))
822                 continue
823             if d.endswith('_dump'):
824                 if d in svcs:
825                     continue
826                 if d[:-5]+'_details' in msgs:
827                     s['Service'].append(Service(d, d[:-5]+'_details',
828                                                 stream=True))
829                 else:
830                     raise ValueError('{} missing details message'
831                                      .format(d))
832                 continue
833
834             if d.endswith('_details'):
835                 if d[:-8]+'_dump' not in msgs:
836                     raise ValueError('{} missing dump message'
837                                      .format(d))
838                 continue
839
840             if d in svcs:
841                 continue
842             if d+'_reply' in msgs:
843                 s['Service'].append(Service(d, d+'_reply'))
844             else:
845                 raise ValueError(
846                     '{} missing reply message ({}) or service definition'
847                     .format(d, d+'_reply'))
848
849         return s
850
851     def process_imports(self, objs, in_import, result):
852         imported_objs = []
853         for o in objs:
854             # Only allow the following object types from imported file
855             if in_import and not (isinstance(o, Enum) or
856                                   isinstance(o, Union) or
857                                   isinstance(o, Typedef) or
858                                   isinstance(o, Import) or
859                                   isinstance(o, Using)):
860                 continue
861             if isinstance(o, Import):
862                 result.append(o)
863                 result = self.process_imports(o.result, True, result)
864             else:
865                 result.append(o)
866         return result
867
868
869 # Add message ids to each message.
870 def add_msg_id(s):
871     for o in s:
872         o.block.insert(0, Field('u16', '_vl_msg_id'))
873     return s
874
875
876 dirlist = []
877
878
879 def dirlist_add(dirs):
880     global dirlist
881     if dirs:
882         dirlist = dirlist + dirs
883
884
885 def dirlist_get():
886     return dirlist
887
888
889 def foldup_blocks(block, crc):
890     for b in block:
891         # Look up CRC in user defined types
892         if b.fieldtype.startswith('vl_api_'):
893             # Recursively
894             t = global_types[b.fieldtype]
895             try:
896                 crc = crc_block_combine(t.block, crc)
897                 return foldup_blocks(t.block, crc)
898             except AttributeError:
899                 pass
900     return crc
901
902
903 def foldup_crcs(s):
904     for f in s:
905         f.crc = foldup_blocks(f.block,
906                               binascii.crc32(f.crc))
907
908
909 #
910 # Main
911 #
912 def main():
913     if sys.version_info < (3, 5,):
914         log.exception('vppapigen requires a supported version of python. '
915                       'Please use version 3.5 or greater. '
916                       'Using {}'.format(sys.version))
917         return 1
918
919     cliparser = argparse.ArgumentParser(description='VPP API generator')
920     cliparser.add_argument('--pluginpath', default=""),
921     cliparser.add_argument('--includedir', action='append'),
922     cliparser.add_argument('--input',
923                            type=argparse.FileType('r', encoding='UTF-8'),
924                            default=sys.stdin)
925     cliparser.add_argument('--output', nargs='?',
926                            type=argparse.FileType('w', encoding='UTF-8'),
927                            default=sys.stdout)
928
929     cliparser.add_argument('output_module', nargs='?', default='C')
930     cliparser.add_argument('--debug', action='store_true')
931     cliparser.add_argument('--show-name', nargs=1)
932     args = cliparser.parse_args()
933
934     dirlist_add(args.includedir)
935     if not args.debug:
936         sys.excepthook = exception_handler
937
938     # Filename
939     if args.show_name:
940         filename = args.show_name[0]
941     elif args.input != sys.stdin:
942         filename = args.input.name
943     else:
944         filename = ''
945
946     if args.debug:
947         logging.basicConfig(stream=sys.stdout, level=logging.WARNING)
948     else:
949         logging.basicConfig()
950
951     parser = VPPAPI(debug=args.debug, filename=filename, logger=log)
952     parsed_objects = parser.parse_file(args.input, log)
953
954     # Build a list of objects. Hash of lists.
955     result = []
956
957     if args.output_module == 'C':
958         s = parser.process(parsed_objects)
959     else:
960         result = parser.process_imports(parsed_objects, False, result)
961         s = parser.process(result)
962
963     # Add msg_id field
964     s['Define'] = add_msg_id(s['Define'])
965
966     # Fold up CRCs
967     foldup_crcs(s['Define'])
968
969     #
970     # Debug
971     if args.debug:
972         import pprint
973         pp = pprint.PrettyPrinter(indent=4, stream=sys.stderr)
974         for t in s['Define']:
975             pp.pprint([t.name, t.flags, t.block])
976         for t in s['types']:
977             pp.pprint([t.name, t.block])
978
979     #
980     # Generate representation
981     #
982     from importlib.machinery import SourceFileLoader
983
984     # Default path
985     pluginpath = ''
986     if not args.pluginpath:
987         cand = []
988         cand.append(os.path.dirname(os.path.realpath(__file__)))
989         cand.append(os.path.dirname(os.path.realpath(__file__)) +
990                     '/../share/vpp/')
991         for c in cand:
992             c += '/'
993             if os.path.isfile('{}vppapigen_{}.py'
994                               .format(c, args.output_module.lower())):
995                 pluginpath = c
996                 break
997     else:
998         pluginpath = args.pluginpath + '/'
999     if pluginpath == '':
1000         log.exception('Output plugin not found')
1001         return 1
1002     module_path = '{}vppapigen_{}.py'.format(pluginpath,
1003                                              args.output_module.lower())
1004
1005     try:
1006         plugin = SourceFileLoader(args.output_module,
1007                                   module_path).load_module()
1008     except Exception as err:
1009         log.exception('Error importing output plugin: {}, {}'
1010                       .format(module_path, err))
1011         return 1
1012
1013     result = plugin.run(filename, s)
1014     if result:
1015         print(result, file=args.output)
1016     else:
1017         log.exception('Running plugin failed: {} {}'
1018                       .format(filename, result))
1019         return 1
1020     return 0
1021
1022
1023 if __name__ == '__main__':
1024     sys.exit(main())