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