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