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