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