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