blob: 9c6b21ed48d9c131ff601c06820a2eb9219a074e [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 = {}
21
22
Ole Troan8dbfb432019-04-24 14:31:18 +020023def global_type_add(name, obj):
Ole Troan9d420872017-10-12 13:06:35 +020024 '''Add new type to the dictionary of types '''
25 type_name = 'vl_api_' + name + '_t'
Ole Troan8dbfb432019-04-24 14:31:18 +020026 global_types[type_name] = obj
Ole Troan9d420872017-10-12 13:06:35 +020027
28
29# All your trace are belong to us!
30def exception_handler(exception_type, exception, traceback):
Ole Troan17225df2018-04-11 09:50:03 +020031 print("%s: %s" % (exception_type.__name__, exception))
Ole Troan9d420872017-10-12 13:06:35 +020032
33
34#
35# Lexer
36#
37class VPPAPILexer(object):
38 def __init__(self, filename):
39 self.filename = filename
40
41 reserved = {
42 'service': 'SERVICE',
43 'rpc': 'RPC',
44 'returns': 'RETURNS',
Marek Gradzki51e59682018-03-06 10:05:44 +010045 'null': 'NULL',
Ole Troan9d420872017-10-12 13:06:35 +020046 'stream': 'STREAM',
47 'events': 'EVENTS',
48 'define': 'DEFINE',
49 'typedef': 'TYPEDEF',
50 'enum': 'ENUM',
51 'typeonly': 'TYPEONLY',
52 'manual_print': 'MANUAL_PRINT',
53 'manual_endian': 'MANUAL_ENDIAN',
54 'dont_trace': 'DONT_TRACE',
55 'autoreply': 'AUTOREPLY',
56 'option': 'OPTION',
57 'u8': 'U8',
58 'u16': 'U16',
59 'u32': 'U32',
60 'u64': 'U64',
61 'i8': 'I8',
62 'i16': 'I16',
63 'i32': 'I32',
64 'i64': 'I64',
65 'f64': 'F64',
66 'bool': 'BOOL',
67 'string': 'STRING',
68 'import': 'IMPORT',
69 'true': 'TRUE',
70 'false': 'FALSE',
Ole Troan2c2feab2018-04-24 00:02:37 -040071 'union': 'UNION',
Ole Troan9d420872017-10-12 13:06:35 +020072 }
73
74 tokens = ['STRING_LITERAL',
75 'ID', 'NUM'] + list(reserved.values())
76
77 t_ignore_LINE_COMMENT = '//.*'
78
79 def t_NUM(self, t):
Ole Troan2a688c02019-06-20 14:06:07 +020080 r'0[xX][0-9a-fA-F]+|-?\d+'
Ole Troan9d420872017-10-12 13:06:35 +020081 base = 16 if t.value.startswith('0x') else 10
82 t.value = int(t.value, base)
83 return t
84
85 def t_ID(self, t):
86 r'[a-zA-Z_][a-zA-Z_0-9]*'
87 # Check for reserved words
88 t.type = VPPAPILexer.reserved.get(t.value, 'ID')
89 return t
90
91 # C string
92 def t_STRING_LITERAL(self, t):
93 r'\"([^\\\n]|(\\.))*?\"'
94 t.value = str(t.value).replace("\"", "")
95 return t
96
97 # C or C++ comment (ignore)
98 def t_comment(self, t):
99 r'(/\*(.|\n)*?\*/)|(//.*)'
100 t.lexer.lineno += t.value.count('\n')
101
102 # Error handling rule
103 def t_error(self, t):
104 raise ParseError("Illegal character '{}' ({})"
105 "in {}: line {}".format(t.value[0],
106 hex(ord(t.value[0])),
107 self.filename,
108 t.lexer.lineno))
109 t.lexer.skip(1)
110
111 # Define a rule so we can track line numbers
112 def t_newline(self, t):
113 r'\n+'
114 t.lexer.lineno += len(t.value)
115
116 literals = ":{}[];=.,"
117
118 # A string containing ignored characters (spaces and tabs)
119 t_ignore = ' \t'
120
Ole Troan17225df2018-04-11 09:50:03 +0200121
Ole Troan8dbfb432019-04-24 14:31:18 +0200122def crc_block_combine(block, crc):
Ole Troan58914252018-10-23 10:50:07 +0200123 s = str(block).encode()
Ole Troan8dbfb432019-04-24 14:31:18 +0200124 return binascii.crc32(s, crc) & 0xffffffff
Ole Troan58914252018-10-23 10:50:07 +0200125
Ole Troan9d420872017-10-12 13:06:35 +0200126class Service():
Paul Vinciguerra7e0c48e2019-02-01 19:37:45 -0800127 def __init__(self, caller, reply, events=None, stream=False):
Ole Troan9d420872017-10-12 13:06:35 +0200128 self.caller = caller
129 self.reply = reply
130 self.stream = stream
Paul Vinciguerra7e0c48e2019-02-01 19:37:45 -0800131 self.events = [] if events is None else events
Ole Troan9d420872017-10-12 13:06:35 +0200132
133
134class Typedef():
135 def __init__(self, name, flags, block):
136 self.name = name
137 self.flags = flags
138 self.block = block
Ole Troan8dbfb432019-04-24 14:31:18 +0200139 self.crc = str(block).encode()
Ole Troan2c2feab2018-04-24 00:02:37 -0400140 self.manual_print = False
141 self.manual_endian = False
142 for f in flags:
143 if f == 'manual_print':
144 self.manual_print = True
145 elif f == 'manual_endian':
146 self.manual_endian = True
Ole Troan8dbfb432019-04-24 14:31:18 +0200147 global_type_add(name, self)
Ole Troan9d420872017-10-12 13:06:35 +0200148
149 def __repr__(self):
150 return self.name + str(self.flags) + str(self.block)
151
152
Ole Troan53fffa12018-11-13 12:36:56 +0100153class Using():
154 def __init__(self, name, alias):
Ole Troan53fffa12018-11-13 12:36:56 +0100155 self.name = name
156
157 if isinstance(alias, Array):
Paul Vinciguerraf4647ed2019-02-12 12:21:01 -0800158 a = { 'type': alias.fieldtype, # noqa: E201
159 'length': alias.length } # noqa: E202
Ole Troan53fffa12018-11-13 12:36:56 +0100160 else:
Paul Vinciguerraf4647ed2019-02-12 12:21:01 -0800161 a = { 'type': alias.fieldtype } # noqa: E201,E202
Ole Troan53fffa12018-11-13 12:36:56 +0100162 self.alias = a
Ole Troan8dbfb432019-04-24 14:31:18 +0200163 self.crc = str(alias).encode()
164 global_type_add(name, self)
Ole Troan53fffa12018-11-13 12:36:56 +0100165
166 def __repr__(self):
167 return self.name + str(self.alias)
168
169
Ole Troan2c2feab2018-04-24 00:02:37 -0400170class Union():
171 def __init__(self, name, block):
172 self.type = 'Union'
173 self.manual_print = False
174 self.manual_endian = False
Ole Troan2c2feab2018-04-24 00:02:37 -0400175 self.name = name
176 self.block = block
Ole Troan8dbfb432019-04-24 14:31:18 +0200177 self.crc = str(block).encode()
178 global_type_add(name, self)
Ole Troan2c2feab2018-04-24 00:02:37 -0400179
180 def __repr__(self):
181 return str(self.block)
182
183
Ole Troan9d420872017-10-12 13:06:35 +0200184class Define():
185 def __init__(self, name, flags, block):
186 self.name = name
187 self.flags = flags
188 self.block = block
Ole Troan8dbfb432019-04-24 14:31:18 +0200189 self.crc = str(block).encode()
Ole Troan9d420872017-10-12 13:06:35 +0200190 self.dont_trace = False
191 self.manual_print = False
192 self.manual_endian = False
193 self.autoreply = False
194 self.singular = False
195 for f in flags:
Ole Troan2c2feab2018-04-24 00:02:37 -0400196 if f == 'dont_trace':
Ole Troan9d420872017-10-12 13:06:35 +0200197 self.dont_trace = True
198 elif f == 'manual_print':
199 self.manual_print = True
200 elif f == 'manual_endian':
201 self.manual_endian = True
202 elif f == 'autoreply':
203 self.autoreply = True
204
205 for b in block:
206 if isinstance(b, Option):
207 if b[1] == 'singular' and b[2] == 'true':
208 self.singular = True
209 block.remove(b)
210
211 def __repr__(self):
212 return self.name + str(self.flags) + str(self.block)
213
214
215class Enum():
216 def __init__(self, name, block, enumtype='u32'):
217 self.name = name
218 self.enumtype = enumtype
Ole Troan2c2feab2018-04-24 00:02:37 -0400219
Ole Troan9d420872017-10-12 13:06:35 +0200220 count = 0
221 for i, b in enumerate(block):
222 if type(b) is list:
223 count = b[1]
224 else:
225 count += 1
226 block[i] = [b, count]
227
228 self.block = block
Ole Troan8dbfb432019-04-24 14:31:18 +0200229 self.crc = str(block).encode()
230 global_type_add(name, self)
Ole Troan9d420872017-10-12 13:06:35 +0200231
232 def __repr__(self):
233 return self.name + str(self.block)
234
235
236class Import():
237 def __init__(self, filename):
238 self.filename = filename
239
240 # Deal with imports
241 parser = VPPAPI(filename=filename)
242 dirlist = dirlist_get()
243 f = filename
244 for dir in dirlist:
245 f = os.path.join(dir, filename)
246 if os.path.exists(f):
247 break
Ole Troan58914252018-10-23 10:50:07 +0200248 if sys.version[0] == '2':
249 with open(f) as fd:
250 self.result = parser.parse_file(fd, None)
251 else:
252 with open(f, encoding='utf-8') as fd:
253 self.result = parser.parse_file(fd, None)
Ole Troan9d420872017-10-12 13:06:35 +0200254
255 def __repr__(self):
256 return self.filename
257
258
259class Option():
260 def __init__(self, option):
261 self.option = option
Ole Troan8dbfb432019-04-24 14:31:18 +0200262 self.crc = str(option).encode()
Ole Troan9d420872017-10-12 13:06:35 +0200263
264 def __repr__(self):
265 return str(self.option)
266
267 def __getitem__(self, index):
268 return self.option[index]
269
270
271class Array():
272 def __init__(self, fieldtype, name, length):
273 self.type = 'Array'
274 self.fieldtype = fieldtype
275 self.fieldname = name
276 if type(length) is str:
277 self.lengthfield = length
278 self.length = 0
279 else:
280 self.length = length
281 self.lengthfield = None
282
283 def __repr__(self):
284 return str([self.fieldtype, self.fieldname, self.length,
285 self.lengthfield])
286
287
288class Field():
Ole Troan9ac11382019-04-23 17:11:01 +0200289 def __init__(self, fieldtype, name, limit=None):
Ole Troan9d420872017-10-12 13:06:35 +0200290 self.type = 'Field'
291 self.fieldtype = fieldtype
292 self.fieldname = name
Ole Troan9ac11382019-04-23 17:11:01 +0200293 self.limit = limit
Ole Troan9d420872017-10-12 13:06:35 +0200294
295 def __repr__(self):
296 return str([self.fieldtype, self.fieldname])
297
298
299class Coord(object):
300 """ Coordinates of a syntactic element. Consists of:
301 - File name
302 - Line number
303 - (optional) column number, for the Lexer
304 """
305 __slots__ = ('file', 'line', 'column', '__weakref__')
306
307 def __init__(self, file, line, column=None):
308 self.file = file
309 self.line = line
310 self.column = column
311
312 def __str__(self):
313 str = "%s:%s" % (self.file, self.line)
314 if self.column:
315 str += ":%s" % self.column
316 return str
317
318
319class ParseError(Exception):
320 pass
321
322
323#
324# Grammar rules
325#
326class VPPAPIParser(object):
327 tokens = VPPAPILexer.tokens
328
329 def __init__(self, filename, logger):
330 self.filename = filename
331 self.logger = logger
332 self.fields = []
333
334 def _parse_error(self, msg, coord):
335 raise ParseError("%s: %s" % (coord, msg))
336
337 def _parse_warning(self, msg, coord):
338 if self.logger:
339 self.logger.warning("%s: %s" % (coord, msg))
340
341 def _coord(self, lineno, column=None):
342 return Coord(
343 file=self.filename,
344 line=lineno, column=column)
345
346 def _token_coord(self, p, token_idx):
347 """ Returns the coordinates for the YaccProduction object 'p' indexed
348 with 'token_idx'. The coordinate includes the 'lineno' and
349 'column'. Both follow the lex semantic, starting from 1.
350 """
351 last_cr = p.lexer.lexdata.rfind('\n', 0, p.lexpos(token_idx))
352 if last_cr < 0:
353 last_cr = -1
354 column = (p.lexpos(token_idx) - (last_cr))
355 return self._coord(p.lineno(token_idx), column)
356
357 def p_slist(self, p):
358 '''slist : stmt
359 | slist stmt'''
360 if len(p) == 2:
361 p[0] = [p[1]]
362 else:
363 p[0] = p[1] + [p[2]]
364
365 def p_stmt(self, p):
366 '''stmt : define
367 | typedef
368 | option
369 | import
370 | enum
Ole Troan2c2feab2018-04-24 00:02:37 -0400371 | union
Ole Troan9d420872017-10-12 13:06:35 +0200372 | service'''
373 p[0] = p[1]
374
375 def p_import(self, p):
376 '''import : IMPORT STRING_LITERAL ';' '''
377 p[0] = Import(p[2])
378
379 def p_service(self, p):
380 '''service : SERVICE '{' service_statements '}' ';' '''
381 p[0] = p[3]
382
383 def p_service_statements(self, p):
384 '''service_statements : service_statement
385 | service_statements service_statement'''
386 if len(p) == 2:
387 p[0] = [p[1]]
388 else:
389 p[0] = p[1] + [p[2]]
390
391 def p_service_statement(self, p):
Marek Gradzki51e59682018-03-06 10:05:44 +0100392 '''service_statement : RPC ID RETURNS NULL ';'
393 | RPC ID RETURNS ID ';'
Ole Troan9d420872017-10-12 13:06:35 +0200394 | RPC ID RETURNS STREAM ID ';'
395 | RPC ID RETURNS ID EVENTS event_list ';' '''
Marek Gradzkifc70e3a2018-03-06 10:56:26 +0100396 if p[2] == p[4]:
397 # Verify that caller and reply differ
Ole Troan17225df2018-04-11 09:50:03 +0200398 self._parse_error(
399 'Reply ID ({}) should not be equal to Caller ID'.format(p[2]),
400 self._token_coord(p, 1))
Ole Troan9d420872017-10-12 13:06:35 +0200401 if len(p) == 8:
402 p[0] = Service(p[2], p[4], p[6])
403 elif len(p) == 7:
404 p[0] = Service(p[2], p[5], stream=True)
405 else:
406 p[0] = Service(p[2], p[4])
407
408 def p_event_list(self, p):
409 '''event_list : events
410 | event_list events '''
411 if len(p) == 2:
412 p[0] = [p[1]]
413 else:
414 p[0] = p[1] + [p[2]]
415
416 def p_event(self, p):
417 '''events : ID
418 | ID ',' '''
419 p[0] = p[1]
420
421 def p_enum(self, p):
422 '''enum : ENUM ID '{' enum_statements '}' ';' '''
423 p[0] = Enum(p[2], p[4])
424
425 def p_enum_type(self, p):
426 ''' enum : ENUM ID ':' enum_size '{' enum_statements '}' ';' '''
427 if len(p) == 9:
428 p[0] = Enum(p[2], p[6], enumtype=p[4])
429 else:
430 p[0] = Enum(p[2], p[4])
431
432 def p_enum_size(self, p):
433 ''' enum_size : U8
434 | U16
435 | U32 '''
436 p[0] = p[1]
437
438 def p_define(self, p):
439 '''define : DEFINE ID '{' block_statements_opt '}' ';' '''
440 self.fields = []
441 p[0] = Define(p[2], [], p[4])
442
443 def p_define_flist(self, p):
444 '''define : flist DEFINE ID '{' block_statements_opt '}' ';' '''
Ole Troan2c2feab2018-04-24 00:02:37 -0400445 # Legacy typedef
446 if 'typeonly' in p[1]:
447 p[0] = Typedef(p[3], p[1], p[5])
448 else:
449 p[0] = Define(p[3], p[1], p[5])
Ole Troan9d420872017-10-12 13:06:35 +0200450
451 def p_flist(self, p):
452 '''flist : flag
453 | flist flag'''
454 if len(p) == 2:
455 p[0] = [p[1]]
456 else:
457 p[0] = p[1] + [p[2]]
458
459 def p_flag(self, p):
460 '''flag : MANUAL_PRINT
461 | MANUAL_ENDIAN
462 | DONT_TRACE
463 | TYPEONLY
464 | AUTOREPLY'''
465 if len(p) == 1:
466 return
467 p[0] = p[1]
468
469 def p_typedef(self, p):
470 '''typedef : TYPEDEF ID '{' block_statements_opt '}' ';' '''
471 p[0] = Typedef(p[2], [], p[4])
472
Ole Troan53fffa12018-11-13 12:36:56 +0100473 def p_typedef_alias(self, p):
474 '''typedef : TYPEDEF declaration '''
475 p[0] = Using(p[2].fieldname, p[2])
476
Ole Troan9d420872017-10-12 13:06:35 +0200477 def p_block_statements_opt(self, p):
Ole Troan2c2feab2018-04-24 00:02:37 -0400478 '''block_statements_opt : block_statements '''
Ole Troan9d420872017-10-12 13:06:35 +0200479 p[0] = p[1]
480
481 def p_block_statements(self, p):
482 '''block_statements : block_statement
483 | block_statements block_statement'''
484 if len(p) == 2:
485 p[0] = [p[1]]
486 else:
487 p[0] = p[1] + [p[2]]
488
489 def p_block_statement(self, p):
490 '''block_statement : declaration
491 | option '''
492 p[0] = p[1]
493
494 def p_enum_statements(self, p):
495 '''enum_statements : enum_statement
Ole Troan9ac11382019-04-23 17:11:01 +0200496 | enum_statements enum_statement'''
Ole Troan9d420872017-10-12 13:06:35 +0200497 if len(p) == 2:
498 p[0] = [p[1]]
499 else:
500 p[0] = p[1] + [p[2]]
501
502 def p_enum_statement(self, p):
503 '''enum_statement : ID '=' NUM ','
504 | ID ',' '''
505 if len(p) == 5:
506 p[0] = [p[1], p[3]]
507 else:
508 p[0] = p[1]
509
Ole Troan85465582019-04-30 10:04:36 +0200510 def p_field_options(self, p):
511 '''field_options : field_option
512 | field_options field_option'''
513 if len(p) == 2:
514 p[0] = p[1]
515 else:
516 p[0] = { **p[1], **p[2] }
517
518 def p_field_option(self, p):
519 '''field_option : ID '=' assignee ','
520 | ID '=' assignee
521 '''
522 p[0] = { p[1]: p[3] }
523
Ole Troan9d420872017-10-12 13:06:35 +0200524 def p_declaration(self, p):
Ole Troan9ac11382019-04-23 17:11:01 +0200525 '''declaration : type_specifier ID ';'
Ole Troan85465582019-04-30 10:04:36 +0200526 | type_specifier ID '[' field_options ']' ';' '''
527 if len(p) == 7:
528 p[0] = Field(p[1], p[2], p[4])
Ole Troan9ac11382019-04-23 17:11:01 +0200529 elif len(p) == 4:
530 p[0] = Field(p[1], p[2])
531 else:
Ole Troan9d420872017-10-12 13:06:35 +0200532 self._parse_error('ERROR')
533 self.fields.append(p[2])
Ole Troan9ac11382019-04-23 17:11:01 +0200534
Ole Troan9d420872017-10-12 13:06:35 +0200535 def p_declaration_array(self, p):
536 '''declaration : type_specifier ID '[' NUM ']' ';'
537 | type_specifier ID '[' ID ']' ';' '''
538 if len(p) != 7:
539 return self._parse_error(
540 'array: %s' % p.value,
541 self._coord(lineno=p.lineno))
542
543 # Make this error later
544 if type(p[4]) is int and p[4] == 0:
545 # XXX: Line number is wrong
546 self._parse_warning('Old Style VLA: {} {}[{}];'
547 .format(p[1], p[2], p[4]),
548 self._token_coord(p, 1))
549
550 if type(p[4]) is str and p[4] not in self.fields:
551 # Verify that length field exists
552 self._parse_error('Missing length field: {} {}[{}];'
553 .format(p[1], p[2], p[4]),
554 self._token_coord(p, 1))
555 p[0] = Array(p[1], p[2], p[4])
556
557 def p_option(self, p):
558 '''option : OPTION ID '=' assignee ';' '''
559 p[0] = Option([p[1], p[2], p[4]])
560
561 def p_assignee(self, p):
562 '''assignee : NUM
563 | TRUE
564 | FALSE
565 | STRING_LITERAL '''
566 p[0] = p[1]
567
568 def p_type_specifier(self, p):
569 '''type_specifier : U8
570 | U16
571 | U32
572 | U64
573 | I8
574 | I16
575 | I32
576 | I64
577 | F64
578 | BOOL
579 | STRING'''
580 p[0] = p[1]
581
582 # Do a second pass later to verify that user defined types are defined
583 def p_typedef_specifier(self, p):
584 '''type_specifier : ID '''
585 if p[1] not in global_types:
586 self._parse_error('Undefined type: {}'.format(p[1]),
587 self._token_coord(p, 1))
588 p[0] = p[1]
589
Ole Troan2c2feab2018-04-24 00:02:37 -0400590 def p_union(self, p):
591 '''union : UNION ID '{' block_statements_opt '}' ';' '''
592 p[0] = Union(p[2], p[4])
593
Ole Troan9d420872017-10-12 13:06:35 +0200594 # Error rule for syntax errors
595 def p_error(self, p):
596 if p:
597 self._parse_error(
598 'before: %s' % p.value,
599 self._coord(lineno=p.lineno))
600 else:
601 self._parse_error('At end of input', self.filename)
602
603
604class VPPAPI(object):
605
606 def __init__(self, debug=False, filename='', logger=None):
607 self.lexer = lex.lex(module=VPPAPILexer(filename), debug=debug)
608 self.parser = yacc.yacc(module=VPPAPIParser(filename, logger),
Ole Troand6743b12018-03-07 08:40:58 +0100609 write_tables=False, debug=debug)
Ole Troan9d420872017-10-12 13:06:35 +0200610 self.logger = logger
611
612 def parse_string(self, code, debug=0, lineno=1):
613 self.lexer.lineno = lineno
614 return self.parser.parse(code, lexer=self.lexer, debug=debug)
615
616 def parse_file(self, fd, debug=0):
617 data = fd.read()
618 return self.parse_string(data, debug=debug)
619
620 def autoreply_block(self, name):
621 block = [Field('u32', 'context'),
622 Field('i32', 'retval')]
623 return Define(name + '_reply', [], block)
624
625 def process(self, objs):
626 s = {}
Ole Troan2c2feab2018-04-24 00:02:37 -0400627 s['Option'] = {}
628 s['Define'] = []
629 s['Service'] = []
630 s['types'] = []
631 s['Import'] = []
Ole Troan53fffa12018-11-13 12:36:56 +0100632 s['Alias'] = {}
Ole Troan8dbfb432019-04-24 14:31:18 +0200633 crc = 0
Ole Troan9d420872017-10-12 13:06:35 +0200634 for o in objs:
Ole Troan2c2feab2018-04-24 00:02:37 -0400635 tname = o.__class__.__name__
Ole Troan8dbfb432019-04-24 14:31:18 +0200636 try:
637 crc = binascii.crc32(o.crc, crc)
638 except AttributeError:
639 pass
Ole Troan9d420872017-10-12 13:06:35 +0200640 if isinstance(o, Define):
Ole Troan2c2feab2018-04-24 00:02:37 -0400641 s[tname].append(o)
642 if o.autoreply:
643 s[tname].append(self.autoreply_block(o.name))
Ole Troan9d420872017-10-12 13:06:35 +0200644 elif isinstance(o, Option):
Ole Troan2c2feab2018-04-24 00:02:37 -0400645 s[tname][o[1]] = o[2]
Ole Troan9d420872017-10-12 13:06:35 +0200646 elif type(o) is list:
647 for o2 in o:
648 if isinstance(o2, Service):
Ole Troan2c2feab2018-04-24 00:02:37 -0400649 s['Service'].append(o2)
Ole Troan58914252018-10-23 10:50:07 +0200650 elif (isinstance(o, Enum) or
651 isinstance(o, Typedef) or
652 isinstance(o, Union)):
Ole Troan2c2feab2018-04-24 00:02:37 -0400653 s['types'].append(o)
Ole Troan53fffa12018-11-13 12:36:56 +0100654 elif isinstance(o, Using):
655 s['Alias'][o.name] = o.alias
Ole Troan2c2feab2018-04-24 00:02:37 -0400656 else:
657 if tname not in s:
Ole Troan58914252018-10-23 10:50:07 +0200658 raise ValueError('Unknown class type: {} {}'
659 .format(tname, o))
Ole Troan2c2feab2018-04-24 00:02:37 -0400660 s[tname].append(o)
Ole Troan9d420872017-10-12 13:06:35 +0200661
Ole Troan2c2feab2018-04-24 00:02:37 -0400662 msgs = {d.name: d for d in s['Define']}
663 svcs = {s.caller: s for s in s['Service']}
664 replies = {s.reply: s for s in s['Service']}
Marek Gradzki51e59682018-03-06 10:05:44 +0100665 seen_services = {}
Ole Troan9d420872017-10-12 13:06:35 +0200666
Ole Troan8dbfb432019-04-24 14:31:18 +0200667 s['file_crc'] = crc
668
Ole Troan9d420872017-10-12 13:06:35 +0200669 for service in svcs:
670 if service not in msgs:
Ole Troan17225df2018-04-11 09:50:03 +0200671 raise ValueError(
672 'Service definition refers to unknown message'
673 ' definition: {}'.format(service))
674 if svcs[service].reply != 'null' and \
675 svcs[service].reply not in msgs:
Ole Troan9d420872017-10-12 13:06:35 +0200676 raise ValueError('Service definition refers to unknown message'
677 ' definition in reply: {}'
678 .format(svcs[service].reply))
Marek Gradzkib533f3f2018-03-06 11:10:56 +0100679 if service in replies:
680 raise ValueError('Service definition refers to message'
681 ' marked as reply: {}'.format(service))
Ole Troan9d420872017-10-12 13:06:35 +0200682 for event in svcs[service].events:
683 if event not in msgs:
684 raise ValueError('Service definition refers to unknown '
685 'event: {} in message: {}'
686 .format(event, service))
Marek Gradzki51e59682018-03-06 10:05:44 +0100687 seen_services[event] = True
Ole Troan9d420872017-10-12 13:06:35 +0200688
Marek Gradzki51e59682018-03-06 10:05:44 +0100689 # Create services implicitly
Ole Troan9d420872017-10-12 13:06:35 +0200690 for d in msgs:
Marek Gradzki51e59682018-03-06 10:05:44 +0100691 if d in seen_services:
692 continue
Ole Troan9d420872017-10-12 13:06:35 +0200693 if msgs[d].singular is True:
694 continue
Ole Troan9d420872017-10-12 13:06:35 +0200695 if d.endswith('_reply'):
696 if d[:-6] in svcs:
697 continue
698 if d[:-6] not in msgs:
Marek Gradzkicc134712018-03-06 12:25:02 +0100699 raise ValueError('{} missing calling message'
700 .format(d))
Ole Troan9d420872017-10-12 13:06:35 +0200701 continue
702 if d.endswith('_dump'):
703 if d in svcs:
704 continue
705 if d[:-5]+'_details' in msgs:
Ole Troan2c2feab2018-04-24 00:02:37 -0400706 s['Service'].append(Service(d, d[:-5]+'_details',
Ole Troan58914252018-10-23 10:50:07 +0200707 stream=True))
Ole Troan9d420872017-10-12 13:06:35 +0200708 else:
Marek Gradzkicc134712018-03-06 12:25:02 +0100709 raise ValueError('{} missing details message'
710 .format(d))
Ole Troan9d420872017-10-12 13:06:35 +0200711 continue
712
713 if d.endswith('_details'):
714 if d[:-8]+'_dump' not in msgs:
Marek Gradzkicc134712018-03-06 12:25:02 +0100715 raise ValueError('{} missing dump message'
716 .format(d))
Ole Troan9d420872017-10-12 13:06:35 +0200717 continue
718
719 if d in svcs:
720 continue
721 if d+'_reply' in msgs:
Ole Troan2c2feab2018-04-24 00:02:37 -0400722 s['Service'].append(Service(d, d+'_reply'))
Ole Troan9d420872017-10-12 13:06:35 +0200723 else:
Ole Troan17225df2018-04-11 09:50:03 +0200724 raise ValueError(
725 '{} missing reply message ({}) or service definition'
726 .format(d, d+'_reply'))
Ole Troan9d420872017-10-12 13:06:35 +0200727
728 return s
729
Ole Troan2c2feab2018-04-24 00:02:37 -0400730 def process_imports(self, objs, in_import, result):
Marek Gradzki51e59682018-03-06 10:05:44 +0100731 imported_objs = []
Ole Troan9d420872017-10-12 13:06:35 +0200732 for o in objs:
Ole Troan2c2feab2018-04-24 00:02:37 -0400733 # Only allow the following object types from imported file
734 if in_import and not (isinstance(o, Enum) or
735 isinstance(o, Union) or
Ole Troan10a09892018-06-29 11:32:33 +0200736 isinstance(o, Typedef) or
Ole Troan53fffa12018-11-13 12:36:56 +0100737 isinstance(o, Import) or
738 isinstance(o, Using)):
Ole Troan2c2feab2018-04-24 00:02:37 -0400739 continue
Ole Troan2c2feab2018-04-24 00:02:37 -0400740 if isinstance(o, Import):
741 self.process_imports(o.result, True, result)
Ole Troan10a09892018-06-29 11:32:33 +0200742 else:
743 result.append(o)
Ole Troan9d420872017-10-12 13:06:35 +0200744
Ole Troan58914252018-10-23 10:50:07 +0200745
Ole Troan9d420872017-10-12 13:06:35 +0200746# Add message ids to each message.
747def add_msg_id(s):
748 for o in s:
749 o.block.insert(0, Field('u16', '_vl_msg_id'))
750 return s
751
752
Ole Troan9d420872017-10-12 13:06:35 +0200753dirlist = []
754
755
756def dirlist_add(dirs):
757 global dirlist
758 if dirs:
759 dirlist = dirlist + dirs
760
761
762def dirlist_get():
763 return dirlist
764
Ole Troan8dbfb432019-04-24 14:31:18 +0200765def foldup_blocks(block, crc):
766 for b in block:
767 # Look up CRC in user defined types
768 if b.fieldtype.startswith('vl_api_'):
769 # Recursively
770 t = global_types[b.fieldtype]
771 try:
772 crc = crc_block_combine(t.block, crc)
773 return foldup_blocks(t.block, crc)
774 except:
775 pass
776 return crc
777
778def foldup_crcs(s):
779 for f in s:
780 f.crc = foldup_blocks(f.block,
781 binascii.crc32(f.crc))
Ole Troan9d420872017-10-12 13:06:35 +0200782
783#
784# Main
785#
786def main():
Ole Troan9d420872017-10-12 13:06:35 +0200787 cliparser = argparse.ArgumentParser(description='VPP API generator')
788 cliparser.add_argument('--pluginpath', default=""),
789 cliparser.add_argument('--includedir', action='append'),
Ole Troan58914252018-10-23 10:50:07 +0200790 if sys.version[0] == '2':
791 cliparser.add_argument('--input', type=argparse.FileType('r'),
792 default=sys.stdin)
Paul Vinciguerra7e0c48e2019-02-01 19:37:45 -0800793 cliparser.add_argument('--output', nargs='?',
794 type=argparse.FileType('w'),
795 default=sys.stdout)
796
Ole Troan58914252018-10-23 10:50:07 +0200797 else:
798 cliparser.add_argument('--input',
799 type=argparse.FileType('r', encoding='UTF-8'),
800 default=sys.stdin)
Paul Vinciguerra7e0c48e2019-02-01 19:37:45 -0800801 cliparser.add_argument('--output', nargs='?',
802 type=argparse.FileType('w', encoding='UTF-8'),
803 default=sys.stdout)
Ole Troan9d420872017-10-12 13:06:35 +0200804
805 cliparser.add_argument('output_module', nargs='?', default='C')
806 cliparser.add_argument('--debug', action='store_true')
807 cliparser.add_argument('--show-name', nargs=1)
808 args = cliparser.parse_args()
809
810 dirlist_add(args.includedir)
811 if not args.debug:
812 sys.excepthook = exception_handler
813
814 # Filename
815 if args.show_name:
816 filename = args.show_name[0]
817 elif args.input != sys.stdin:
818 filename = args.input.name
819 else:
820 filename = ''
821
Marek Gradzki51e59682018-03-06 10:05:44 +0100822 if args.debug:
823 logging.basicConfig(stream=sys.stdout, level=logging.WARNING)
824 else:
825 logging.basicConfig()
826 log = logging.getLogger('vppapigen')
827
Ole Troan9d420872017-10-12 13:06:35 +0200828 parser = VPPAPI(debug=args.debug, filename=filename, logger=log)
Ole Troan2c2feab2018-04-24 00:02:37 -0400829 parsed_objects = parser.parse_file(args.input, log)
Ole Troan9d420872017-10-12 13:06:35 +0200830
831 # Build a list of objects. Hash of lists.
Ole Troan2c2feab2018-04-24 00:02:37 -0400832 result = []
833 parser.process_imports(parsed_objects, False, result)
Ole Troan9d420872017-10-12 13:06:35 +0200834 s = parser.process(result)
835
836 # Add msg_id field
Ole Troan2c2feab2018-04-24 00:02:37 -0400837 s['Define'] = add_msg_id(s['Define'])
Ole Troan9d420872017-10-12 13:06:35 +0200838
Ole Troan8dbfb432019-04-24 14:31:18 +0200839 # Fold up CRCs
840 foldup_crcs(s['Define'])
Ole Troan9d420872017-10-12 13:06:35 +0200841
842 #
843 # Debug
844 if args.debug:
845 import pprint
Ole Troan10a09892018-06-29 11:32:33 +0200846 pp = pprint.PrettyPrinter(indent=4, stream=sys.stderr)
Ole Troan2c2feab2018-04-24 00:02:37 -0400847 for t in s['Define']:
Ole Troan9d420872017-10-12 13:06:35 +0200848 pp.pprint([t.name, t.flags, t.block])
Ole Troan2c2feab2018-04-24 00:02:37 -0400849 for t in s['types']:
850 pp.pprint([t.name, t.block])
Ole Troan9d420872017-10-12 13:06:35 +0200851
852 #
853 # Generate representation
854 #
Paul Vinciguerraf4647ed2019-02-12 12:21:01 -0800855 from importlib.machinery import SourceFileLoader
Ole Troan9d420872017-10-12 13:06:35 +0200856
857 # Default path
Ole Troan30787372018-03-01 13:33:39 +0100858 pluginpath = ''
Ole Troan9d420872017-10-12 13:06:35 +0200859 if not args.pluginpath:
Ole Troan30787372018-03-01 13:33:39 +0100860 cand = []
861 cand.append(os.path.dirname(os.path.realpath(__file__)))
Ole Troan17225df2018-04-11 09:50:03 +0200862 cand.append(os.path.dirname(os.path.realpath(__file__)) +
Ole Troan30787372018-03-01 13:33:39 +0100863 '/../share/vpp/')
864 for c in cand:
865 c += '/'
Ole Troan58914252018-10-23 10:50:07 +0200866 if os.path.isfile('{}vppapigen_{}.py'
867 .format(c, args.output_module.lower())):
Ole Troan30787372018-03-01 13:33:39 +0100868 pluginpath = c
869 break
Ole Troan9d420872017-10-12 13:06:35 +0200870 else:
871 pluginpath = args.pluginpath + '/'
Ole Troan30787372018-03-01 13:33:39 +0100872 if pluginpath == '':
873 raise Exception('Output plugin not found')
Ole Troan58914252018-10-23 10:50:07 +0200874 module_path = '{}vppapigen_{}.py'.format(pluginpath,
875 args.output_module.lower())
Ole Troan9d420872017-10-12 13:06:35 +0200876
877 try:
Paul Vinciguerraf4647ed2019-02-12 12:21:01 -0800878 plugin = SourceFileLoader(args.output_module,
879 module_path).load_module()
Ole Troan58914252018-10-23 10:50:07 +0200880 except Exception as err:
Ole Troan9d420872017-10-12 13:06:35 +0200881 raise Exception('Error importing output plugin: {}, {}'
882 .format(module_path, err))
883
Ole Troan8dbfb432019-04-24 14:31:18 +0200884 result = plugin.run(filename, s)
Ole Troan9d420872017-10-12 13:06:35 +0200885 if result:
Ole Troan17225df2018-04-11 09:50:03 +0200886 print(result, file=args.output)
Ole Troan9d420872017-10-12 13:06:35 +0200887 else:
888 raise Exception('Running plugin failed: {} {}'
889 .format(filename, result))
890
891
892if __name__ == '__main__':
893 main()