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