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