api: split api generated files
[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.dont_trace = False
263         self.manual_print = False
264         self.manual_endian = False
265         self.autoreply = False
266         self.singular = False
267         self.options = {}
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                 else:
283                     self.options[b.option] = b.value
284                 block.remove(b)
285
286         self.vla = vla_is_last_check(name, block)
287         self.crc = str(block).encode()
288
289     def __repr__(self):
290         return self.name + str(self.flags) + str(self.block)
291
292
293 class Enum():
294     def __init__(self, name, block, enumtype='u32'):
295         self.name = name
296         self.enumtype = enumtype
297         self.vla = False
298
299         count = 0
300         for i, b in enumerate(block):
301             if type(b) is list:
302                 count = b[1]
303             else:
304                 count += 1
305                 block[i] = [b, count]
306
307         self.block = block
308         self.crc = str(block).encode()
309         global_type_add(name, self)
310
311     def __repr__(self):
312         return self.name + str(self.block)
313
314
315 class Import():
316
317     def __new__(cls, *args, **kwargs):
318         if args[0] not in seen_imports:
319             instance = super().__new__(cls)
320             instance._initialized = False
321             seen_imports[args[0]] = instance
322
323         return seen_imports[args[0]]
324
325     def __init__(self, filename):
326         if self._initialized:
327             return
328         else:
329             self.filename = filename
330             # Deal with imports
331             parser = VPPAPI(filename=filename)
332             dirlist = dirlist_get()
333             f = filename
334             for dir in dirlist:
335                 f = os.path.join(dir, filename)
336                 if os.path.exists(f):
337                     break
338             if sys.version[0] == '2':
339                 with open(f) as fd:
340                     self.result = parser.parse_file(fd, None)
341             else:
342                 with open(f, encoding='utf-8') as fd:
343                     self.result = parser.parse_file(fd, None)
344             self._initialized = True
345
346     def __repr__(self):
347         return self.filename
348
349
350 class Option():
351     def __init__(self, option, value):
352         self.type = 'Option'
353         self.option = option
354         self.value = value
355         self.crc = str(option).encode()
356
357     def __repr__(self):
358         return str(self.option)
359
360     def __getitem__(self, index):
361         return self.option[index]
362
363
364 class Array():
365     def __init__(self, fieldtype, name, length, modern_vla=False):
366         self.type = 'Array'
367         self.fieldtype = fieldtype
368         self.fieldname = name
369         self.modern_vla = modern_vla
370         if type(length) is str:
371             self.lengthfield = length
372             self.length = 0
373             self.vla = True
374         else:
375             self.length = length
376             self.lengthfield = None
377             self.vla = False
378
379     def __repr__(self):
380         return str([self.fieldtype, self.fieldname, self.length,
381                     self.lengthfield])
382
383
384 class Field():
385     def __init__(self, fieldtype, name, limit=None):
386         self.type = 'Field'
387         self.fieldtype = fieldtype
388
389         if self.fieldtype == 'string':
390             raise ValueError("The string type {!r} is an "
391                              "array type ".format(name))
392
393         if name in keyword.kwlist:
394             raise ValueError("Fieldname {!r} is a python keyword and is not "
395                              "accessible via the python API. ".format(name))
396         self.fieldname = name
397         self.limit = limit
398
399     def __repr__(self):
400         return str([self.fieldtype, self.fieldname])
401
402
403 class Coord(object):
404     """ Coordinates of a syntactic element. Consists of:
405             - File name
406             - Line number
407             - (optional) column number, for the Lexer
408     """
409     __slots__ = ('file', 'line', 'column', '__weakref__')
410
411     def __init__(self, file, line, column=None):
412         self.file = file
413         self.line = line
414         self.column = column
415
416     def __str__(self):
417         str = "%s:%s" % (self.file, self.line)
418         if self.column:
419             str += ":%s" % self.column
420         return str
421
422
423 class ParseError(Exception):
424     pass
425
426
427 #
428 # Grammar rules
429 #
430 class VPPAPIParser(object):
431     tokens = VPPAPILexer.tokens
432
433     def __init__(self, filename, logger):
434         self.filename = filename
435         self.logger = logger
436         self.fields = []
437
438     def _parse_error(self, msg, coord):
439         raise ParseError("%s: %s" % (coord, msg))
440
441     def _parse_warning(self, msg, coord):
442         if self.logger:
443             self.logger.warning("%s: %s" % (coord, msg))
444
445     def _coord(self, lineno, column=None):
446         return Coord(
447                 file=self.filename,
448                 line=lineno, column=column)
449
450     def _token_coord(self, p, token_idx):
451         """ Returns the coordinates for the YaccProduction object 'p' indexed
452             with 'token_idx'. The coordinate includes the 'lineno' and
453             'column'. Both follow the lex semantic, starting from 1.
454         """
455         last_cr = p.lexer.lexdata.rfind('\n', 0, p.lexpos(token_idx))
456         if last_cr < 0:
457             last_cr = -1
458         column = (p.lexpos(token_idx) - (last_cr))
459         return self._coord(p.lineno(token_idx), column)
460
461     def p_slist(self, p):
462         '''slist : stmt
463                  | slist stmt'''
464         if len(p) == 2:
465             p[0] = [p[1]]
466         else:
467             p[0] = p[1] + [p[2]]
468
469     def p_stmt(self, p):
470         '''stmt : define
471                 | typedef
472                 | option
473                 | import
474                 | enum
475                 | union
476                 | service'''
477         p[0] = p[1]
478
479     def p_import(self, p):
480         '''import : IMPORT STRING_LITERAL ';' '''
481         p[0] = Import(p[2])
482
483     def p_service(self, p):
484         '''service : SERVICE '{' service_statements '}' ';' '''
485         p[0] = p[3]
486
487     def p_service_statements(self, p):
488         '''service_statements : service_statement
489                         | service_statements service_statement'''
490         if len(p) == 2:
491             p[0] = [p[1]]
492         else:
493             p[0] = p[1] + [p[2]]
494
495     def p_service_statement(self, p):
496         '''service_statement : RPC ID RETURNS NULL ';'
497                              | RPC ID RETURNS ID ';'
498                              | RPC ID RETURNS STREAM ID ';'
499                              | RPC ID RETURNS ID EVENTS event_list ';' '''
500         if p[2] == p[4]:
501             # Verify that caller and reply differ
502             self._parse_error(
503                 'Reply ID ({}) should not be equal to Caller ID'.format(p[2]),
504                 self._token_coord(p, 1))
505         if len(p) == 8:
506             p[0] = Service(p[2], p[4], p[6])
507         elif len(p) == 7:
508             p[0] = Service(p[2], p[5], stream=True)
509         else:
510             p[0] = Service(p[2], p[4])
511
512     def p_event_list(self, p):
513         '''event_list : events
514                       | event_list events '''
515         if len(p) == 2:
516             p[0] = [p[1]]
517         else:
518             p[0] = p[1] + [p[2]]
519
520     def p_event(self, p):
521         '''events : ID
522                   | ID ',' '''
523         p[0] = p[1]
524
525     def p_enum(self, p):
526         '''enum : ENUM ID '{' enum_statements '}' ';' '''
527         p[0] = Enum(p[2], p[4])
528
529     def p_enum_type(self, p):
530         ''' enum : ENUM ID ':' enum_size '{' enum_statements '}' ';' '''
531         if len(p) == 9:
532             p[0] = Enum(p[2], p[6], enumtype=p[4])
533         else:
534             p[0] = Enum(p[2], p[4])
535
536     def p_enum_size(self, p):
537         ''' enum_size : U8
538                       | U16
539                       | U32 '''
540         p[0] = p[1]
541
542     def p_define(self, p):
543         '''define : DEFINE ID '{' block_statements_opt '}' ';' '''
544         self.fields = []
545         p[0] = Define(p[2], [], p[4])
546
547     def p_define_flist(self, p):
548         '''define : flist DEFINE ID '{' block_statements_opt '}' ';' '''
549         # Legacy typedef
550         if 'typeonly' in p[1]:
551             self._parse_error('legacy typedef. use typedef: {} {}[{}];'
552                               .format(p[1], p[2], p[4]),
553                               self._token_coord(p, 1))
554         else:
555             p[0] = Define(p[3], p[1], p[5])
556
557     def p_flist(self, p):
558         '''flist : flag
559                  | flist flag'''
560         if len(p) == 2:
561             p[0] = [p[1]]
562         else:
563             p[0] = p[1] + [p[2]]
564
565     def p_flag(self, p):
566         '''flag : MANUAL_PRINT
567                 | MANUAL_ENDIAN
568                 | DONT_TRACE
569                 | TYPEONLY
570                 | AUTOREPLY'''
571         if len(p) == 1:
572             return
573         p[0] = p[1]
574
575     def p_typedef(self, p):
576         '''typedef : TYPEDEF ID '{' block_statements_opt '}' ';' '''
577         p[0] = Typedef(p[2], [], p[4])
578
579     def p_typedef_flist(self, p):
580         '''typedef : flist TYPEDEF ID '{' block_statements_opt '}' ';' '''
581         p[0] = Typedef(p[3], p[1], p[5])
582
583     def p_typedef_alias(self, p):
584         '''typedef : TYPEDEF declaration '''
585         p[0] = Using(p[2].fieldname, [], p[2])
586
587     def p_typedef_alias_flist(self, p):
588         '''typedef : flist TYPEDEF declaration '''
589         p[0] = Using(p[3].fieldname, p[1], p[3])
590
591     def p_block_statements_opt(self, p):
592         '''block_statements_opt : block_statements '''
593         p[0] = p[1]
594
595     def p_block_statements(self, p):
596         '''block_statements : block_statement
597                             | block_statements block_statement'''
598         if len(p) == 2:
599             p[0] = [p[1]]
600         else:
601             p[0] = p[1] + [p[2]]
602
603     def p_block_statement(self, p):
604         '''block_statement : declaration
605                            | option '''
606         p[0] = p[1]
607
608     def p_enum_statements(self, p):
609         '''enum_statements : enum_statement
610                            | enum_statements enum_statement'''
611         if len(p) == 2:
612             p[0] = [p[1]]
613         else:
614             p[0] = p[1] + [p[2]]
615
616     def p_enum_statement(self, p):
617         '''enum_statement : ID '=' NUM ','
618                           | ID ',' '''
619         if len(p) == 5:
620             p[0] = [p[1], p[3]]
621         else:
622             p[0] = p[1]
623
624     def p_field_options(self, p):
625         '''field_options : field_option
626                            | field_options field_option'''
627         if len(p) == 2:
628             p[0] = p[1]
629         else:
630             p[0] = {**p[1], **p[2]}
631
632     def p_field_option(self, p):
633         '''field_option : ID
634                         | ID '=' assignee ','
635                         | ID '=' assignee
636
637         '''
638         if len(p) == 2:
639             p[0] = {p[1]: None}
640         else:
641             p[0] = {p[1]: p[3]}
642
643     def p_declaration(self, p):
644         '''declaration : type_specifier ID ';'
645                        | type_specifier ID '[' field_options ']' ';' '''
646         if len(p) == 7:
647             p[0] = Field(p[1], p[2], p[4])
648         elif len(p) == 4:
649             p[0] = Field(p[1], p[2])
650         else:
651             self._parse_error('ERROR')
652         self.fields.append(p[2])
653
654     def p_declaration_array_vla(self, p):
655         '''declaration : type_specifier ID '[' ']' ';' '''
656         p[0] = Array(p[1], p[2], 0, modern_vla=True)
657
658     def p_declaration_array(self, p):
659         '''declaration : type_specifier ID '[' NUM ']' ';'
660                        | type_specifier ID '[' ID ']' ';' '''
661
662         if len(p) != 7:
663             return self._parse_error(
664                 'array: %s' % p.value,
665                 self._coord(lineno=p.lineno))
666
667         # Make this error later
668         if type(p[4]) is int and p[4] == 0:
669             # XXX: Line number is wrong
670             self._parse_warning('Old Style VLA: {} {}[{}];'
671                                 .format(p[1], p[2], p[4]),
672                                 self._token_coord(p, 1))
673
674         if type(p[4]) is str and p[4] not in self.fields:
675             # Verify that length field exists
676             self._parse_error('Missing length field: {} {}[{}];'
677                               .format(p[1], p[2], p[4]),
678                               self._token_coord(p, 1))
679         p[0] = Array(p[1], p[2], p[4])
680
681     def p_option(self, p):
682         '''option : OPTION ID '=' assignee ';' '''
683         p[0] = Option(p[2], p[4])
684
685     def p_assignee(self, p):
686         '''assignee : NUM
687                     | TRUE
688                     | FALSE
689                     | STRING_LITERAL '''
690         p[0] = p[1]
691
692     def p_type_specifier(self, p):
693         '''type_specifier : U8
694                           | U16
695                           | U32
696                           | U64
697                           | I8
698                           | I16
699                           | I32
700                           | I64
701                           | F64
702                           | BOOL
703                           | STRING'''
704         p[0] = p[1]
705
706     # Do a second pass later to verify that user defined types are defined
707     def p_typedef_specifier(self, p):
708         '''type_specifier : ID '''
709         if p[1] not in global_types:
710             self._parse_error('Undefined type: {}'.format(p[1]),
711                               self._token_coord(p, 1))
712         p[0] = p[1]
713
714     def p_union(self, p):
715         '''union : UNION ID '{' block_statements_opt '}' ';' '''
716         p[0] = Union(p[2], [], p[4])
717
718     def p_union_flist(self, p):
719         '''union : flist UNION ID '{' block_statements_opt '}' ';' '''
720         p[0] = Union(p[3], p[1], p[5])
721
722     # Error rule for syntax errors
723     def p_error(self, p):
724         if p:
725             self._parse_error(
726                 'before: %s' % p.value,
727                 self._coord(lineno=p.lineno))
728         else:
729             self._parse_error('At end of input', self.filename)
730
731
732 class VPPAPI(object):
733
734     def __init__(self, debug=False, filename='', logger=None):
735         self.lexer = lex.lex(module=VPPAPILexer(filename), debug=debug)
736         self.parser = yacc.yacc(module=VPPAPIParser(filename, logger),
737                                 write_tables=False, debug=debug)
738         self.logger = logger
739
740     def parse_string(self, code, debug=0, lineno=1):
741         self.lexer.lineno = lineno
742         return self.parser.parse(code, lexer=self.lexer, debug=debug)
743
744     def parse_file(self, fd, debug=0):
745         data = fd.read()
746         return self.parse_string(data, debug=debug)
747
748     def autoreply_block(self, name):
749         block = [Field('u32', 'context'),
750                  Field('i32', 'retval')]
751         return Define(name + '_reply', [], block)
752
753     def process(self, objs):
754         s = {}
755         s['Option'] = {}
756         s['Define'] = []
757         s['Service'] = []
758         s['types'] = []
759         s['Import'] = []
760         crc = 0
761         for o in objs:
762             tname = o.__class__.__name__
763             try:
764                 crc = binascii.crc32(o.crc, crc)
765             except AttributeError:
766                 pass
767             if isinstance(o, Define):
768                 s[tname].append(o)
769                 if o.autoreply:
770                     s[tname].append(self.autoreply_block(o.name))
771             elif isinstance(o, Option):
772                 s[tname][o[1]] = o[2]
773             elif type(o) is list:
774                 for o2 in o:
775                     if isinstance(o2, Service):
776                         s['Service'].append(o2)
777             elif (isinstance(o, Enum) or
778                   isinstance(o, Typedef) or
779                   isinstance(o, Using) or
780                   isinstance(o, Union)):
781                 s['types'].append(o)
782             else:
783                 if tname not in s:
784                     raise ValueError('Unknown class type: {} {}'
785                                      .format(tname, o))
786                 s[tname].append(o)
787
788         msgs = {d.name: d for d in s['Define']}
789         svcs = {s.caller: s for s in s['Service']}
790         replies = {s.reply: s for s in s['Service']}
791         seen_services = {}
792
793         s['file_crc'] = crc
794
795         for service in svcs:
796             if service not in msgs:
797                 raise ValueError(
798                     'Service definition refers to unknown message'
799                     ' definition: {}'.format(service))
800             if svcs[service].reply != 'null' and \
801                svcs[service].reply not in msgs:
802                 raise ValueError('Service definition refers to unknown message'
803                                  ' definition in reply: {}'
804                                  .format(svcs[service].reply))
805             if service in replies:
806                 raise ValueError('Service definition refers to message'
807                                  ' marked as reply: {}'.format(service))
808             for event in svcs[service].events:
809                 if event not in msgs:
810                     raise ValueError('Service definition refers to unknown '
811                                      'event: {} in message: {}'
812                                      .format(event, service))
813                 seen_services[event] = True
814
815         # Create services implicitly
816         for d in msgs:
817             if d in seen_services:
818                 continue
819             if msgs[d].singular is True:
820                 continue
821             if d.endswith('_reply'):
822                 if d[:-6] in svcs:
823                     continue
824                 if d[:-6] not in msgs:
825                     raise ValueError('{} missing calling message'
826                                      .format(d))
827                 continue
828             if d.endswith('_dump'):
829                 if d in svcs:
830                     continue
831                 if d[:-5]+'_details' in msgs:
832                     s['Service'].append(Service(d, d[:-5]+'_details',
833                                                 stream=True))
834                 else:
835                     raise ValueError('{} missing details message'
836                                      .format(d))
837                 continue
838
839             if d.endswith('_details'):
840                 if d[:-8]+'_dump' not in msgs:
841                     raise ValueError('{} missing dump message'
842                                      .format(d))
843                 continue
844
845             if d in svcs:
846                 continue
847             if d+'_reply' in msgs:
848                 s['Service'].append(Service(d, d+'_reply'))
849             else:
850                 raise ValueError(
851                     '{} missing reply message ({}) or service definition'
852                     .format(d, d+'_reply'))
853
854         return s
855
856     def process_imports(self, objs, in_import, result):
857         imported_objs = []
858         for o in objs:
859             # Only allow the following object types from imported file
860             if in_import and not (isinstance(o, Enum) or
861                                   isinstance(o, Union) or
862                                   isinstance(o, Typedef) or
863                                   isinstance(o, Import) or
864                                   isinstance(o, Using)):
865                 continue
866             if isinstance(o, Import):
867                 result.append(o)
868                 result = self.process_imports(o.result, True, result)
869             else:
870                 result.append(o)
871         return result
872
873
874 # Add message ids to each message.
875 def add_msg_id(s):
876     for o in s:
877         o.block.insert(0, Field('u16', '_vl_msg_id'))
878     return s
879
880
881 dirlist = []
882
883
884 def dirlist_add(dirs):
885     global dirlist
886     if dirs:
887         dirlist = dirlist + dirs
888
889
890 def dirlist_get():
891     return dirlist
892
893
894 def foldup_blocks(block, crc):
895     for b in block:
896         # Look up CRC in user defined types
897         if b.fieldtype.startswith('vl_api_'):
898             # Recursively
899             t = global_types[b.fieldtype]
900             try:
901                 crc = crc_block_combine(t.block, crc)
902                 return foldup_blocks(t.block, crc)
903             except AttributeError:
904                 pass
905     return crc
906
907
908 def foldup_crcs(s):
909     for f in s:
910         f.crc = foldup_blocks(f.block,
911                               binascii.crc32(f.crc))
912
913
914 #
915 # Main
916 #
917 def main():
918     if sys.version_info < (3, 5,):
919         log.exception('vppapigen requires a supported version of python. '
920                       'Please use version 3.5 or greater. '
921                       'Using {}'.format(sys.version))
922         return 1
923
924     cliparser = argparse.ArgumentParser(description='VPP API generator')
925     cliparser.add_argument('--pluginpath', default=""),
926     cliparser.add_argument('--includedir', action='append'),
927     cliparser.add_argument('--outputdir', action='store'),
928     cliparser.add_argument('--input',
929                            type=argparse.FileType('r', encoding='UTF-8'),
930                            default=sys.stdin)
931     cliparser.add_argument('--output', nargs='?',
932                            type=argparse.FileType('w', encoding='UTF-8'),
933                            default=sys.stdout)
934
935     cliparser.add_argument('output_module', nargs='?', default='C')
936     cliparser.add_argument('--debug', action='store_true')
937     cliparser.add_argument('--show-name', nargs=1)
938     args = cliparser.parse_args()
939
940     dirlist_add(args.includedir)
941     if not args.debug:
942         sys.excepthook = exception_handler
943
944     # Filename
945     if args.show_name:
946         filename = args.show_name[0]
947     elif args.input != sys.stdin:
948         filename = args.input.name
949     else:
950         filename = ''
951
952     if args.debug:
953         logging.basicConfig(stream=sys.stdout, level=logging.WARNING)
954     else:
955         logging.basicConfig()
956
957     parser = VPPAPI(debug=args.debug, filename=filename, logger=log)
958     parsed_objects = parser.parse_file(args.input, log)
959
960     # Build a list of objects. Hash of lists.
961     result = []
962
963     if args.output_module == 'C':
964         s = parser.process(parsed_objects)
965     else:
966         result = parser.process_imports(parsed_objects, False, result)
967         s = parser.process(result)
968
969     # Add msg_id field
970     s['Define'] = add_msg_id(s['Define'])
971
972     # Fold up CRCs
973     foldup_crcs(s['Define'])
974
975     #
976     # Debug
977     if args.debug:
978         import pprint
979         pp = pprint.PrettyPrinter(indent=4, stream=sys.stderr)
980         for t in s['Define']:
981             pp.pprint([t.name, t.flags, t.block])
982         for t in s['types']:
983             pp.pprint([t.name, t.block])
984
985     #
986     # Generate representation
987     #
988     from importlib.machinery import SourceFileLoader
989
990     # Default path
991     pluginpath = ''
992     if not args.pluginpath:
993         cand = []
994         cand.append(os.path.dirname(os.path.realpath(__file__)))
995         cand.append(os.path.dirname(os.path.realpath(__file__)) +
996                     '/../share/vpp/')
997         for c in cand:
998             c += '/'
999             if os.path.isfile('{}vppapigen_{}.py'
1000                               .format(c, args.output_module.lower())):
1001                 pluginpath = c
1002                 break
1003     else:
1004         pluginpath = args.pluginpath + '/'
1005     if pluginpath == '':
1006         log.exception('Output plugin not found')
1007         return 1
1008     module_path = '{}vppapigen_{}.py'.format(pluginpath,
1009                                              args.output_module.lower())
1010
1011     try:
1012         plugin = SourceFileLoader(args.output_module,
1013                                   module_path).load_module()
1014     except Exception as err:
1015         log.exception('Error importing output plugin: {}, {}'
1016                       .format(module_path, err))
1017         return 1
1018
1019     result = plugin.run(args, filename, s)
1020     if result:
1021         print(result, file=args.output)
1022     else:
1023         log.exception('Running plugin failed: {} {}'
1024                       .format(filename, result))
1025         return 1
1026     return 0
1027
1028
1029 if __name__ == '__main__':
1030     sys.exit(main())