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