blob: ae2b0b1ba40c5496e947f358bbcc7b7237f91112 [file] [log] [blame]
Paul Vinciguerraf4647ed2019-02-12 12:21:01 -08001#!/usr/bin/python3
Ole Troan9d420872017-10-12 13:06:35 +02002
3from __future__ import print_function
4import ply.lex as lex
5import ply.yacc as yacc
6import sys
7import argparse
8import logging
9import binascii
10import os
11
Ole Troand6743b12018-03-07 08:40:58 +010012# Ensure we don't leave temporary files around
13sys.dont_write_bytecode = True
14
Ole Troan9d420872017-10-12 13:06:35 +020015#
16# VPP API language
17#
18
19# Global dictionary of new types (including enums)
20global_types = {}
Ole Troan17225df2018-04-11 09:50:03 +020021global_crc = 0
Ole Troan9d420872017-10-12 13:06:35 +020022
23
24def 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!
33def exception_handler(exception_type, exception, traceback):
Ole Troan17225df2018-04-11 09:50:03 +020034 print("%s: %s" % (exception_type.__name__, exception))
Ole Troan9d420872017-10-12 13:06:35 +020035
36
37#
38# Lexer
39#
40class VPPAPILexer(object):
41 def __init__(self, filename):
42 self.filename = filename
43
44 reserved = {
45 'service': 'SERVICE',
46 'rpc': 'RPC',
47 'returns': 'RETURNS',
Marek Gradzki51e59682018-03-06 10:05:44 +010048 'null': 'NULL',
Ole Troan9d420872017-10-12 13:06:35 +020049 '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',
Ole Troan2c2feab2018-04-24 00:02:37 -040074 'union': 'UNION',
Ole Troan9d420872017-10-12 13:06:35 +020075 }
76
77 tokens = ['STRING_LITERAL',
78 'ID', 'NUM'] + list(reserved.values())
79
80 t_ignore_LINE_COMMENT = '//.*'
81
82 def t_NUM(self, t):
83 r'0[xX][0-9a-fA-F]+|\d+'
84 base = 16 if t.value.startswith('0x') else 10
85 t.value = int(t.value, base)
86 return t
87
88 def t_ID(self, t):
89 r'[a-zA-Z_][a-zA-Z_0-9]*'
90 # Check for reserved words
91 t.type = VPPAPILexer.reserved.get(t.value, 'ID')
92 return t
93
94 # C string
95 def t_STRING_LITERAL(self, t):
96 r'\"([^\\\n]|(\\.))*?\"'
97 t.value = str(t.value).replace("\"", "")
98 return t
99
100 # C or C++ comment (ignore)
101 def t_comment(self, t):
102 r'(/\*(.|\n)*?\*/)|(//.*)'
103 t.lexer.lineno += t.value.count('\n')
104
105 # Error handling rule
106 def t_error(self, t):
107 raise ParseError("Illegal character '{}' ({})"
108 "in {}: line {}".format(t.value[0],
109 hex(ord(t.value[0])),
110 self.filename,
111 t.lexer.lineno))
112 t.lexer.skip(1)
113
114 # Define a rule so we can track line numbers
115 def t_newline(self, t):
116 r'\n+'
117 t.lexer.lineno += len(t.value)
118
119 literals = ":{}[];=.,"
120
121 # A string containing ignored characters (spaces and tabs)
122 t_ignore = ' \t'
123
Ole Troan17225df2018-04-11 09:50:03 +0200124
Ole Troan58914252018-10-23 10:50:07 +0200125#
126# Side-effect: Sets global_crc
127#
128def crc_block(block):
129 global global_crc
130 s = str(block).encode()
131 global_crc = binascii.crc32(s, global_crc)
132 return binascii.crc32(s) & 0xffffffff
133
134
Ole Troan9d420872017-10-12 13:06:35 +0200135class Service():
Paul Vinciguerra7e0c48e2019-02-01 19:37:45 -0800136 def __init__(self, caller, reply, events=None, stream=False):
Ole Troan9d420872017-10-12 13:06:35 +0200137 self.caller = caller
138 self.reply = reply
139 self.stream = stream
Paul Vinciguerra7e0c48e2019-02-01 19:37:45 -0800140 self.events = [] if events is None else events
Ole Troan9d420872017-10-12 13:06:35 +0200141
142
143class Typedef():
144 def __init__(self, name, flags, block):
145 self.name = name
146 self.flags = flags
147 self.block = block
Ole Troan58914252018-10-23 10:50:07 +0200148 self.crc = crc_block(block)
Ole Troan2c2feab2018-04-24 00:02:37 -0400149 self.manual_print = False
150 self.manual_endian = False
151 for f in flags:
152 if f == 'manual_print':
153 self.manual_print = True
154 elif f == 'manual_endian':
155 self.manual_endian = True
Ole Troan9d420872017-10-12 13:06:35 +0200156 global_type_add(name)
157
158 def __repr__(self):
159 return self.name + str(self.flags) + str(self.block)
160
161
Ole Troan53fffa12018-11-13 12:36:56 +0100162class Using():
163 def __init__(self, name, alias):
164 global global_crc
165 self.name = name
166
167 if isinstance(alias, Array):
Paul Vinciguerraf4647ed2019-02-12 12:21:01 -0800168 a = { 'type': alias.fieldtype, # noqa: E201
169 'length': alias.length } # noqa: E202
Ole Troan53fffa12018-11-13 12:36:56 +0100170 else:
Paul Vinciguerraf4647ed2019-02-12 12:21:01 -0800171 a = { 'type': alias.fieldtype } # noqa: E201,E202
Ole Troan53fffa12018-11-13 12:36:56 +0100172 self.alias = a
Paul Vinciguerra7e0c48e2019-02-01 19:37:45 -0800173 self.crc = binascii.crc32(str(alias).encode()) & 0xffffffff
174 global_crc = binascii.crc32(str(alias).encode(), global_crc)
Ole Troan53fffa12018-11-13 12:36:56 +0100175 global_type_add(name)
176
177 def __repr__(self):
178 return self.name + str(self.alias)
179
180
Ole Troan2c2feab2018-04-24 00:02:37 -0400181class Union():
182 def __init__(self, name, block):
183 self.type = 'Union'
184 self.manual_print = False
185 self.manual_endian = False
186 global global_crc
187 self.name = name
188 self.block = block
Ole Troan58914252018-10-23 10:50:07 +0200189 self.crc = crc_block(block)
Ole Troan2c2feab2018-04-24 00:02:37 -0400190 global_type_add(name)
191
192 def __repr__(self):
193 return str(self.block)
194
195
Ole Troan9d420872017-10-12 13:06:35 +0200196class Define():
197 def __init__(self, name, flags, block):
198 self.name = name
199 self.flags = flags
200 self.block = block
Ole Troan58914252018-10-23 10:50:07 +0200201 self.crc = crc_block(block)
Ole Troan9d420872017-10-12 13:06:35 +0200202 self.dont_trace = False
203 self.manual_print = False
204 self.manual_endian = False
205 self.autoreply = False
206 self.singular = False
207 for f in flags:
Ole Troan2c2feab2018-04-24 00:02:37 -0400208 if f == 'dont_trace':
Ole Troan9d420872017-10-12 13:06:35 +0200209 self.dont_trace = True
210 elif f == 'manual_print':
211 self.manual_print = True
212 elif f == 'manual_endian':
213 self.manual_endian = True
214 elif f == 'autoreply':
215 self.autoreply = True
216
217 for b in block:
218 if isinstance(b, Option):
219 if b[1] == 'singular' and b[2] == 'true':
220 self.singular = True
221 block.remove(b)
222
223 def __repr__(self):
224 return self.name + str(self.flags) + str(self.block)
225
226
227class Enum():
228 def __init__(self, name, block, enumtype='u32'):
229 self.name = name
230 self.enumtype = enumtype
Ole Troan2c2feab2018-04-24 00:02:37 -0400231
Ole Troan9d420872017-10-12 13:06:35 +0200232 count = 0
233 for i, b in enumerate(block):
234 if type(b) is list:
235 count = b[1]
236 else:
237 count += 1
238 block[i] = [b, count]
239
240 self.block = block
Ole Troan58914252018-10-23 10:50:07 +0200241 self.crc = crc_block(block)
Ole Troan9d420872017-10-12 13:06:35 +0200242 global_type_add(name)
243
244 def __repr__(self):
245 return self.name + str(self.block)
246
247
248class Import():
249 def __init__(self, filename):
250 self.filename = filename
251
252 # Deal with imports
253 parser = VPPAPI(filename=filename)
254 dirlist = dirlist_get()
255 f = filename
256 for dir in dirlist:
257 f = os.path.join(dir, filename)
258 if os.path.exists(f):
259 break
Ole Troan58914252018-10-23 10:50:07 +0200260 if sys.version[0] == '2':
261 with open(f) as fd:
262 self.result = parser.parse_file(fd, None)
263 else:
264 with open(f, encoding='utf-8') as fd:
265 self.result = parser.parse_file(fd, None)
Ole Troan9d420872017-10-12 13:06:35 +0200266
267 def __repr__(self):
268 return self.filename
269
270
271class Option():
272 def __init__(self, option):
273 self.option = option
Ole Troan58914252018-10-23 10:50:07 +0200274 self.crc = crc_block(option)
Ole Troan9d420872017-10-12 13:06:35 +0200275
276 def __repr__(self):
277 return str(self.option)
278
279 def __getitem__(self, index):
280 return self.option[index]
281
282
283class Array():
284 def __init__(self, fieldtype, name, length):
285 self.type = 'Array'
286 self.fieldtype = fieldtype
287 self.fieldname = name
288 if type(length) is str:
289 self.lengthfield = length
290 self.length = 0
291 else:
292 self.length = length
293 self.lengthfield = None
294
295 def __repr__(self):
296 return str([self.fieldtype, self.fieldname, self.length,
297 self.lengthfield])
298
299
300class Field():
Ole Troan9ac11382019-04-23 17:11:01 +0200301 def __init__(self, fieldtype, name, limit=None):
Ole Troan9d420872017-10-12 13:06:35 +0200302 self.type = 'Field'
303 self.fieldtype = fieldtype
304 self.fieldname = name
Ole Troan9ac11382019-04-23 17:11:01 +0200305 self.limit = limit
Ole Troan9d420872017-10-12 13:06:35 +0200306
307 def __repr__(self):
308 return str([self.fieldtype, self.fieldname])
309
310
311class Coord(object):
312 """ Coordinates of a syntactic element. Consists of:
313 - File name
314 - Line number
315 - (optional) column number, for the Lexer
316 """
317 __slots__ = ('file', 'line', 'column', '__weakref__')
318
319 def __init__(self, file, line, column=None):
320 self.file = file
321 self.line = line
322 self.column = column
323
324 def __str__(self):
325 str = "%s:%s" % (self.file, self.line)
326 if self.column:
327 str += ":%s" % self.column
328 return str
329
330
331class ParseError(Exception):
332 pass
333
334
335#
336# Grammar rules
337#
338class VPPAPIParser(object):
339 tokens = VPPAPILexer.tokens
340
341 def __init__(self, filename, logger):
342 self.filename = filename
343 self.logger = logger
344 self.fields = []
345
346 def _parse_error(self, msg, coord):
347 raise ParseError("%s: %s" % (coord, msg))
348
349 def _parse_warning(self, msg, coord):
350 if self.logger:
351 self.logger.warning("%s: %s" % (coord, msg))
352
353 def _coord(self, lineno, column=None):
354 return Coord(
355 file=self.filename,
356 line=lineno, column=column)
357
358 def _token_coord(self, p, token_idx):
359 """ Returns the coordinates for the YaccProduction object 'p' indexed
360 with 'token_idx'. The coordinate includes the 'lineno' and
361 'column'. Both follow the lex semantic, starting from 1.
362 """
363 last_cr = p.lexer.lexdata.rfind('\n', 0, p.lexpos(token_idx))
364 if last_cr < 0:
365 last_cr = -1
366 column = (p.lexpos(token_idx) - (last_cr))
367 return self._coord(p.lineno(token_idx), column)
368
369 def p_slist(self, p):
370 '''slist : stmt
371 | slist stmt'''
372 if len(p) == 2:
373 p[0] = [p[1]]
374 else:
375 p[0] = p[1] + [p[2]]
376
377 def p_stmt(self, p):
378 '''stmt : define
379 | typedef
380 | option
381 | import
382 | enum
Ole Troan2c2feab2018-04-24 00:02:37 -0400383 | union
Ole Troan9d420872017-10-12 13:06:35 +0200384 | service'''
385 p[0] = p[1]
386
387 def p_import(self, p):
388 '''import : IMPORT STRING_LITERAL ';' '''
389 p[0] = Import(p[2])
390
391 def p_service(self, p):
392 '''service : SERVICE '{' service_statements '}' ';' '''
393 p[0] = p[3]
394
395 def p_service_statements(self, p):
396 '''service_statements : service_statement
397 | service_statements service_statement'''
398 if len(p) == 2:
399 p[0] = [p[1]]
400 else:
401 p[0] = p[1] + [p[2]]
402
403 def p_service_statement(self, p):
Marek Gradzki51e59682018-03-06 10:05:44 +0100404 '''service_statement : RPC ID RETURNS NULL ';'
405 | RPC ID RETURNS ID ';'
Ole Troan9d420872017-10-12 13:06:35 +0200406 | RPC ID RETURNS STREAM ID ';'
407 | RPC ID RETURNS ID EVENTS event_list ';' '''
Marek Gradzkifc70e3a2018-03-06 10:56:26 +0100408 if p[2] == p[4]:
409 # Verify that caller and reply differ
Ole Troan17225df2018-04-11 09:50:03 +0200410 self._parse_error(
411 'Reply ID ({}) should not be equal to Caller ID'.format(p[2]),
412 self._token_coord(p, 1))
Ole Troan9d420872017-10-12 13:06:35 +0200413 if len(p) == 8:
414 p[0] = Service(p[2], p[4], p[6])
415 elif len(p) == 7:
416 p[0] = Service(p[2], p[5], stream=True)
417 else:
418 p[0] = Service(p[2], p[4])
419
420 def p_event_list(self, p):
421 '''event_list : events
422 | event_list events '''
423 if len(p) == 2:
424 p[0] = [p[1]]
425 else:
426 p[0] = p[1] + [p[2]]
427
428 def p_event(self, p):
429 '''events : ID
430 | ID ',' '''
431 p[0] = p[1]
432
433 def p_enum(self, p):
434 '''enum : ENUM ID '{' enum_statements '}' ';' '''
435 p[0] = Enum(p[2], p[4])
436
437 def p_enum_type(self, p):
438 ''' enum : ENUM ID ':' enum_size '{' enum_statements '}' ';' '''
439 if len(p) == 9:
440 p[0] = Enum(p[2], p[6], enumtype=p[4])
441 else:
442 p[0] = Enum(p[2], p[4])
443
444 def p_enum_size(self, p):
445 ''' enum_size : U8
446 | U16
447 | U32 '''
448 p[0] = p[1]
449
450 def p_define(self, p):
451 '''define : DEFINE ID '{' block_statements_opt '}' ';' '''
452 self.fields = []
453 p[0] = Define(p[2], [], p[4])
454
455 def p_define_flist(self, p):
456 '''define : flist DEFINE ID '{' block_statements_opt '}' ';' '''
Ole Troan2c2feab2018-04-24 00:02:37 -0400457 # Legacy typedef
458 if 'typeonly' in p[1]:
459 p[0] = Typedef(p[3], p[1], p[5])
460 else:
461 p[0] = Define(p[3], p[1], p[5])
Ole Troan9d420872017-10-12 13:06:35 +0200462
463 def p_flist(self, p):
464 '''flist : flag
465 | flist flag'''
466 if len(p) == 2:
467 p[0] = [p[1]]
468 else:
469 p[0] = p[1] + [p[2]]
470
471 def p_flag(self, p):
472 '''flag : MANUAL_PRINT
473 | MANUAL_ENDIAN
474 | DONT_TRACE
475 | TYPEONLY
476 | AUTOREPLY'''
477 if len(p) == 1:
478 return
479 p[0] = p[1]
480
481 def p_typedef(self, p):
482 '''typedef : TYPEDEF ID '{' block_statements_opt '}' ';' '''
483 p[0] = Typedef(p[2], [], p[4])
484
Ole Troan53fffa12018-11-13 12:36:56 +0100485 def p_typedef_alias(self, p):
486 '''typedef : TYPEDEF declaration '''
487 p[0] = Using(p[2].fieldname, p[2])
488
Ole Troan9d420872017-10-12 13:06:35 +0200489 def p_block_statements_opt(self, p):
Ole Troan2c2feab2018-04-24 00:02:37 -0400490 '''block_statements_opt : block_statements '''
Ole Troan9d420872017-10-12 13:06:35 +0200491 p[0] = p[1]
492
493 def p_block_statements(self, p):
494 '''block_statements : block_statement
495 | block_statements block_statement'''
496 if len(p) == 2:
497 p[0] = [p[1]]
498 else:
499 p[0] = p[1] + [p[2]]
500
501 def p_block_statement(self, p):
502 '''block_statement : declaration
503 | option '''
504 p[0] = p[1]
505
506 def p_enum_statements(self, p):
507 '''enum_statements : enum_statement
Ole Troan9ac11382019-04-23 17:11:01 +0200508 | enum_statements enum_statement'''
Ole Troan9d420872017-10-12 13:06:35 +0200509 if len(p) == 2:
510 p[0] = [p[1]]
511 else:
512 p[0] = p[1] + [p[2]]
513
514 def p_enum_statement(self, p):
515 '''enum_statement : ID '=' NUM ','
516 | ID ',' '''
517 if len(p) == 5:
518 p[0] = [p[1], p[3]]
519 else:
520 p[0] = p[1]
521
522 def p_declaration(self, p):
Ole Troan9ac11382019-04-23 17:11:01 +0200523 '''declaration : type_specifier ID ';'
524 | type_specifier ID '[' ID '=' assignee ']' ';' '''
525 if len(p) == 9:
526 p[0] = Field(p[1], p[2], {p[4]: p[6]})
527 elif len(p) == 4:
528 p[0] = Field(p[1], p[2])
529 else:
Ole Troan9d420872017-10-12 13:06:35 +0200530 self._parse_error('ERROR')
531 self.fields.append(p[2])
Ole Troan9ac11382019-04-23 17:11:01 +0200532
Ole Troan9d420872017-10-12 13:06:35 +0200533
534 def p_declaration_array(self, p):
535 '''declaration : type_specifier ID '[' NUM ']' ';'
536 | type_specifier ID '[' ID ']' ';' '''
537 if len(p) != 7:
538 return self._parse_error(
539 'array: %s' % p.value,
540 self._coord(lineno=p.lineno))
541
542 # Make this error later
543 if type(p[4]) is int and p[4] == 0:
544 # XXX: Line number is wrong
545 self._parse_warning('Old Style VLA: {} {}[{}];'
546 .format(p[1], p[2], p[4]),
547 self._token_coord(p, 1))
548
549 if type(p[4]) is str and p[4] not in self.fields:
550 # Verify that length field exists
551 self._parse_error('Missing length field: {} {}[{}];'
552 .format(p[1], p[2], p[4]),
553 self._token_coord(p, 1))
554 p[0] = Array(p[1], p[2], p[4])
555
556 def p_option(self, p):
557 '''option : OPTION ID '=' assignee ';' '''
558 p[0] = Option([p[1], p[2], p[4]])
559
560 def p_assignee(self, p):
561 '''assignee : NUM
562 | TRUE
563 | FALSE
564 | STRING_LITERAL '''
565 p[0] = p[1]
566
567 def p_type_specifier(self, p):
568 '''type_specifier : U8
569 | U16
570 | U32
571 | U64
572 | I8
573 | I16
574 | I32
575 | I64
576 | F64
577 | BOOL
578 | STRING'''
579 p[0] = p[1]
580
581 # Do a second pass later to verify that user defined types are defined
582 def p_typedef_specifier(self, p):
583 '''type_specifier : ID '''
584 if p[1] not in global_types:
585 self._parse_error('Undefined type: {}'.format(p[1]),
586 self._token_coord(p, 1))
587 p[0] = p[1]
588
Ole Troan2c2feab2018-04-24 00:02:37 -0400589 def p_union(self, p):
590 '''union : UNION ID '{' block_statements_opt '}' ';' '''
591 p[0] = Union(p[2], p[4])
592
Ole Troan9d420872017-10-12 13:06:35 +0200593 # Error rule for syntax errors
594 def p_error(self, p):
595 if p:
596 self._parse_error(
597 'before: %s' % p.value,
598 self._coord(lineno=p.lineno))
599 else:
600 self._parse_error('At end of input', self.filename)
601
602
603class VPPAPI(object):
604
605 def __init__(self, debug=False, filename='', logger=None):
606 self.lexer = lex.lex(module=VPPAPILexer(filename), debug=debug)
607 self.parser = yacc.yacc(module=VPPAPIParser(filename, logger),
Ole Troand6743b12018-03-07 08:40:58 +0100608 write_tables=False, debug=debug)
Ole Troan9d420872017-10-12 13:06:35 +0200609 self.logger = logger
610
611 def parse_string(self, code, debug=0, lineno=1):
612 self.lexer.lineno = lineno
613 return self.parser.parse(code, lexer=self.lexer, debug=debug)
614
615 def parse_file(self, fd, debug=0):
616 data = fd.read()
617 return self.parse_string(data, debug=debug)
618
619 def autoreply_block(self, name):
620 block = [Field('u32', 'context'),
621 Field('i32', 'retval')]
622 return Define(name + '_reply', [], block)
623
624 def process(self, objs):
625 s = {}
Ole Troan2c2feab2018-04-24 00:02:37 -0400626 s['Option'] = {}
627 s['Define'] = []
628 s['Service'] = []
629 s['types'] = []
630 s['Import'] = []
Ole Troan53fffa12018-11-13 12:36:56 +0100631 s['Alias'] = {}
Ole Troan9d420872017-10-12 13:06:35 +0200632 for o in objs:
Ole Troan2c2feab2018-04-24 00:02:37 -0400633 tname = o.__class__.__name__
Ole Troan9d420872017-10-12 13:06:35 +0200634 if isinstance(o, Define):
Ole Troan2c2feab2018-04-24 00:02:37 -0400635 s[tname].append(o)
636 if o.autoreply:
637 s[tname].append(self.autoreply_block(o.name))
Ole Troan9d420872017-10-12 13:06:35 +0200638 elif isinstance(o, Option):
Ole Troan2c2feab2018-04-24 00:02:37 -0400639 s[tname][o[1]] = o[2]
Ole Troan9d420872017-10-12 13:06:35 +0200640 elif type(o) is list:
641 for o2 in o:
642 if isinstance(o2, Service):
Ole Troan2c2feab2018-04-24 00:02:37 -0400643 s['Service'].append(o2)
Ole Troan58914252018-10-23 10:50:07 +0200644 elif (isinstance(o, Enum) or
645 isinstance(o, Typedef) or
646 isinstance(o, Union)):
Ole Troan2c2feab2018-04-24 00:02:37 -0400647 s['types'].append(o)
Ole Troan53fffa12018-11-13 12:36:56 +0100648 elif isinstance(o, Using):
649 s['Alias'][o.name] = o.alias
Ole Troan2c2feab2018-04-24 00:02:37 -0400650 else:
651 if tname not in s:
Ole Troan58914252018-10-23 10:50:07 +0200652 raise ValueError('Unknown class type: {} {}'
653 .format(tname, o))
Ole Troan2c2feab2018-04-24 00:02:37 -0400654 s[tname].append(o)
Ole Troan9d420872017-10-12 13:06:35 +0200655
Ole Troan2c2feab2018-04-24 00:02:37 -0400656 msgs = {d.name: d for d in s['Define']}
657 svcs = {s.caller: s for s in s['Service']}
658 replies = {s.reply: s for s in s['Service']}
Marek Gradzki51e59682018-03-06 10:05:44 +0100659 seen_services = {}
Ole Troan9d420872017-10-12 13:06:35 +0200660
661 for service in svcs:
662 if service not in msgs:
Ole Troan17225df2018-04-11 09:50:03 +0200663 raise ValueError(
664 'Service definition refers to unknown message'
665 ' definition: {}'.format(service))
666 if svcs[service].reply != 'null' and \
667 svcs[service].reply not in msgs:
Ole Troan9d420872017-10-12 13:06:35 +0200668 raise ValueError('Service definition refers to unknown message'
669 ' definition in reply: {}'
670 .format(svcs[service].reply))
Marek Gradzkib533f3f2018-03-06 11:10:56 +0100671 if service in replies:
672 raise ValueError('Service definition refers to message'
673 ' marked as reply: {}'.format(service))
Ole Troan9d420872017-10-12 13:06:35 +0200674 for event in svcs[service].events:
675 if event not in msgs:
676 raise ValueError('Service definition refers to unknown '
677 'event: {} in message: {}'
678 .format(event, service))
Marek Gradzki51e59682018-03-06 10:05:44 +0100679 seen_services[event] = True
Ole Troan9d420872017-10-12 13:06:35 +0200680
Marek Gradzki51e59682018-03-06 10:05:44 +0100681 # Create services implicitly
Ole Troan9d420872017-10-12 13:06:35 +0200682 for d in msgs:
Marek Gradzki51e59682018-03-06 10:05:44 +0100683 if d in seen_services:
684 continue
Ole Troan9d420872017-10-12 13:06:35 +0200685 if msgs[d].singular is True:
686 continue
Ole Troan9d420872017-10-12 13:06:35 +0200687 if d.endswith('_reply'):
688 if d[:-6] in svcs:
689 continue
690 if d[:-6] not in msgs:
Marek Gradzkicc134712018-03-06 12:25:02 +0100691 raise ValueError('{} missing calling message'
692 .format(d))
Ole Troan9d420872017-10-12 13:06:35 +0200693 continue
694 if d.endswith('_dump'):
695 if d in svcs:
696 continue
697 if d[:-5]+'_details' in msgs:
Ole Troan2c2feab2018-04-24 00:02:37 -0400698 s['Service'].append(Service(d, d[:-5]+'_details',
Ole Troan58914252018-10-23 10:50:07 +0200699 stream=True))
Ole Troan9d420872017-10-12 13:06:35 +0200700 else:
Marek Gradzkicc134712018-03-06 12:25:02 +0100701 raise ValueError('{} missing details message'
702 .format(d))
Ole Troan9d420872017-10-12 13:06:35 +0200703 continue
704
705 if d.endswith('_details'):
706 if d[:-8]+'_dump' not in msgs:
Marek Gradzkicc134712018-03-06 12:25:02 +0100707 raise ValueError('{} missing dump message'
708 .format(d))
Ole Troan9d420872017-10-12 13:06:35 +0200709 continue
710
711 if d in svcs:
712 continue
713 if d+'_reply' in msgs:
Ole Troan2c2feab2018-04-24 00:02:37 -0400714 s['Service'].append(Service(d, d+'_reply'))
Ole Troan9d420872017-10-12 13:06:35 +0200715 else:
Ole Troan17225df2018-04-11 09:50:03 +0200716 raise ValueError(
717 '{} missing reply message ({}) or service definition'
718 .format(d, d+'_reply'))
Ole Troan9d420872017-10-12 13:06:35 +0200719
720 return s
721
Ole Troan2c2feab2018-04-24 00:02:37 -0400722 def process_imports(self, objs, in_import, result):
Marek Gradzki51e59682018-03-06 10:05:44 +0100723 imported_objs = []
Ole Troan9d420872017-10-12 13:06:35 +0200724 for o in objs:
Ole Troan2c2feab2018-04-24 00:02:37 -0400725 # Only allow the following object types from imported file
726 if in_import and not (isinstance(o, Enum) or
727 isinstance(o, Union) or
Ole Troan10a09892018-06-29 11:32:33 +0200728 isinstance(o, Typedef) or
Ole Troan53fffa12018-11-13 12:36:56 +0100729 isinstance(o, Import) or
730 isinstance(o, Using)):
Ole Troan2c2feab2018-04-24 00:02:37 -0400731 continue
Ole Troan2c2feab2018-04-24 00:02:37 -0400732 if isinstance(o, Import):
733 self.process_imports(o.result, True, result)
Ole Troan10a09892018-06-29 11:32:33 +0200734 else:
735 result.append(o)
Ole Troan9d420872017-10-12 13:06:35 +0200736
Ole Troan58914252018-10-23 10:50:07 +0200737
Ole Troan9d420872017-10-12 13:06:35 +0200738# Add message ids to each message.
739def add_msg_id(s):
740 for o in s:
741 o.block.insert(0, Field('u16', '_vl_msg_id'))
742 return s
743
744
Ole Troan9d420872017-10-12 13:06:35 +0200745dirlist = []
746
747
748def dirlist_add(dirs):
749 global dirlist
750 if dirs:
751 dirlist = dirlist + dirs
752
753
754def dirlist_get():
755 return dirlist
756
757
758#
759# Main
760#
761def main():
Ole Troan9d420872017-10-12 13:06:35 +0200762 cliparser = argparse.ArgumentParser(description='VPP API generator')
763 cliparser.add_argument('--pluginpath', default=""),
764 cliparser.add_argument('--includedir', action='append'),
Ole Troan58914252018-10-23 10:50:07 +0200765 if sys.version[0] == '2':
766 cliparser.add_argument('--input', type=argparse.FileType('r'),
767 default=sys.stdin)
Paul Vinciguerra7e0c48e2019-02-01 19:37:45 -0800768 cliparser.add_argument('--output', nargs='?',
769 type=argparse.FileType('w'),
770 default=sys.stdout)
771
Ole Troan58914252018-10-23 10:50:07 +0200772 else:
773 cliparser.add_argument('--input',
774 type=argparse.FileType('r', encoding='UTF-8'),
775 default=sys.stdin)
Paul Vinciguerra7e0c48e2019-02-01 19:37:45 -0800776 cliparser.add_argument('--output', nargs='?',
777 type=argparse.FileType('w', encoding='UTF-8'),
778 default=sys.stdout)
Ole Troan9d420872017-10-12 13:06:35 +0200779
780 cliparser.add_argument('output_module', nargs='?', default='C')
781 cliparser.add_argument('--debug', action='store_true')
782 cliparser.add_argument('--show-name', nargs=1)
783 args = cliparser.parse_args()
784
785 dirlist_add(args.includedir)
786 if not args.debug:
787 sys.excepthook = exception_handler
788
789 # Filename
790 if args.show_name:
791 filename = args.show_name[0]
792 elif args.input != sys.stdin:
793 filename = args.input.name
794 else:
795 filename = ''
796
Marek Gradzki51e59682018-03-06 10:05:44 +0100797 if args.debug:
798 logging.basicConfig(stream=sys.stdout, level=logging.WARNING)
799 else:
800 logging.basicConfig()
801 log = logging.getLogger('vppapigen')
802
Ole Troan9d420872017-10-12 13:06:35 +0200803 parser = VPPAPI(debug=args.debug, filename=filename, logger=log)
Ole Troan2c2feab2018-04-24 00:02:37 -0400804 parsed_objects = parser.parse_file(args.input, log)
Ole Troan9d420872017-10-12 13:06:35 +0200805
806 # Build a list of objects. Hash of lists.
Ole Troan2c2feab2018-04-24 00:02:37 -0400807 result = []
808 parser.process_imports(parsed_objects, False, result)
Ole Troan9d420872017-10-12 13:06:35 +0200809 s = parser.process(result)
810
811 # Add msg_id field
Ole Troan2c2feab2018-04-24 00:02:37 -0400812 s['Define'] = add_msg_id(s['Define'])
Ole Troan9d420872017-10-12 13:06:35 +0200813
Ole Troan17225df2018-04-11 09:50:03 +0200814 file_crc = global_crc & 0xffffffff
Ole Troan9d420872017-10-12 13:06:35 +0200815
816 #
817 # Debug
818 if args.debug:
819 import pprint
Ole Troan10a09892018-06-29 11:32:33 +0200820 pp = pprint.PrettyPrinter(indent=4, stream=sys.stderr)
Ole Troan2c2feab2018-04-24 00:02:37 -0400821 for t in s['Define']:
Ole Troan9d420872017-10-12 13:06:35 +0200822 pp.pprint([t.name, t.flags, t.block])
Ole Troan2c2feab2018-04-24 00:02:37 -0400823 for t in s['types']:
824 pp.pprint([t.name, t.block])
Ole Troan9d420872017-10-12 13:06:35 +0200825
826 #
827 # Generate representation
828 #
Paul Vinciguerraf4647ed2019-02-12 12:21:01 -0800829 from importlib.machinery import SourceFileLoader
Ole Troan9d420872017-10-12 13:06:35 +0200830
831 # Default path
Ole Troan30787372018-03-01 13:33:39 +0100832 pluginpath = ''
Ole Troan9d420872017-10-12 13:06:35 +0200833 if not args.pluginpath:
Ole Troan30787372018-03-01 13:33:39 +0100834 cand = []
835 cand.append(os.path.dirname(os.path.realpath(__file__)))
Ole Troan17225df2018-04-11 09:50:03 +0200836 cand.append(os.path.dirname(os.path.realpath(__file__)) +
Ole Troan30787372018-03-01 13:33:39 +0100837 '/../share/vpp/')
838 for c in cand:
839 c += '/'
Ole Troan58914252018-10-23 10:50:07 +0200840 if os.path.isfile('{}vppapigen_{}.py'
841 .format(c, args.output_module.lower())):
Ole Troan30787372018-03-01 13:33:39 +0100842 pluginpath = c
843 break
Ole Troan9d420872017-10-12 13:06:35 +0200844 else:
845 pluginpath = args.pluginpath + '/'
Ole Troan30787372018-03-01 13:33:39 +0100846 if pluginpath == '':
847 raise Exception('Output plugin not found')
Ole Troan58914252018-10-23 10:50:07 +0200848 module_path = '{}vppapigen_{}.py'.format(pluginpath,
849 args.output_module.lower())
Ole Troan9d420872017-10-12 13:06:35 +0200850
851 try:
Paul Vinciguerraf4647ed2019-02-12 12:21:01 -0800852 plugin = SourceFileLoader(args.output_module,
853 module_path).load_module()
Ole Troan58914252018-10-23 10:50:07 +0200854 except Exception as err:
Ole Troan9d420872017-10-12 13:06:35 +0200855 raise Exception('Error importing output plugin: {}, {}'
856 .format(module_path, err))
857
858 result = plugin.run(filename, s, file_crc)
859 if result:
Ole Troan17225df2018-04-11 09:50:03 +0200860 print(result, file=args.output)
Ole Troan9d420872017-10-12 13:06:35 +0200861 else:
862 raise Exception('Running plugin failed: {} {}'
863 .format(filename, result))
864
865
866if __name__ == '__main__':
867 main()