blob: 8ae991c9c95daac6735be0a7f35ec4babb618449 [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):
Paul Vinciguerra063f3742019-07-02 13:00:58 -040080 r'0[xX][0-9a-fA-F]+|-?\d+\.?\d*'
Ole Troan9d420872017-10-12 13:06:35 +020081 base = 16 if t.value.startswith('0x') else 10
Paul Vinciguerra063f3742019-07-02 13:00:58 -040082 if '.' in t.value:
83 t.value = float(t.value)
84 else:
85 t.value = int(t.value, base)
Ole Troan9d420872017-10-12 13:06:35 +020086 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 Troan8dbfb432019-04-24 14:31:18 +0200125def crc_block_combine(block, crc):
Ole Troan58914252018-10-23 10:50:07 +0200126 s = str(block).encode()
Ole Troan8dbfb432019-04-24 14:31:18 +0200127 return binascii.crc32(s, crc) & 0xffffffff
Ole Troan58914252018-10-23 10:50:07 +0200128
Paul Vinciguerra063f3742019-07-02 13:00:58 -0400129
Ole Troan9d420872017-10-12 13:06:35 +0200130class Service():
Paul Vinciguerra7e0c48e2019-02-01 19:37:45 -0800131 def __init__(self, caller, reply, events=None, stream=False):
Ole Troan9d420872017-10-12 13:06:35 +0200132 self.caller = caller
133 self.reply = reply
134 self.stream = stream
Paul Vinciguerra7e0c48e2019-02-01 19:37:45 -0800135 self.events = [] if events is None else events
Ole Troan9d420872017-10-12 13:06:35 +0200136
137
138class Typedef():
139 def __init__(self, name, flags, block):
140 self.name = name
141 self.flags = flags
142 self.block = block
Ole Troan8dbfb432019-04-24 14:31:18 +0200143 self.crc = str(block).encode()
Ole Troan2c2feab2018-04-24 00:02:37 -0400144 self.manual_print = False
145 self.manual_endian = False
146 for f in flags:
147 if f == 'manual_print':
148 self.manual_print = True
149 elif f == 'manual_endian':
150 self.manual_endian = True
Ole Troan8dbfb432019-04-24 14:31:18 +0200151 global_type_add(name, self)
Ole Troan9d420872017-10-12 13:06:35 +0200152
153 def __repr__(self):
Vratko Polak7520e172019-08-01 10:31:49 +0200154 return self.name + str(self.flags) + str(self.block)
Ole Troan9d420872017-10-12 13:06:35 +0200155
156
Ole Troan53fffa12018-11-13 12:36:56 +0100157class Using():
158 def __init__(self, name, alias):
Ole Troan53fffa12018-11-13 12:36:56 +0100159 self.name = name
160
161 if isinstance(alias, Array):
Vratko Polak7520e172019-08-01 10:31:49 +0200162 a = { 'type': alias.fieldtype, # noqa: E201
163 'length': alias.length } # noqa: E202
Ole Troan53fffa12018-11-13 12:36:56 +0100164 else:
Vratko Polak7520e172019-08-01 10:31:49 +0200165 a = { 'type': alias.fieldtype } # noqa: E201,E202
Ole Troan53fffa12018-11-13 12:36:56 +0100166 self.alias = a
Ole Troan8dbfb432019-04-24 14:31:18 +0200167 self.crc = str(alias).encode()
168 global_type_add(name, self)
Ole Troan53fffa12018-11-13 12:36:56 +0100169
170 def __repr__(self):
Vratko Polak7520e172019-08-01 10:31:49 +0200171 return self.name + str(self.alias)
Ole Troan53fffa12018-11-13 12:36:56 +0100172
173
Ole Troan2c2feab2018-04-24 00:02:37 -0400174class Union():
175 def __init__(self, name, block):
176 self.type = 'Union'
177 self.manual_print = False
178 self.manual_endian = False
Ole Troan2c2feab2018-04-24 00:02:37 -0400179 self.name = name
180 self.block = block
Ole Troan8dbfb432019-04-24 14:31:18 +0200181 self.crc = str(block).encode()
182 global_type_add(name, self)
Ole Troan2c2feab2018-04-24 00:02:37 -0400183
184 def __repr__(self):
Vratko Polak7520e172019-08-01 10:31:49 +0200185 return str(self.block)
Ole Troan2c2feab2018-04-24 00:02:37 -0400186
187
Ole Troan9d420872017-10-12 13:06:35 +0200188class Define():
189 def __init__(self, name, flags, block):
190 self.name = name
191 self.flags = flags
192 self.block = block
Ole Troan8dbfb432019-04-24 14:31:18 +0200193 self.crc = str(block).encode()
Ole Troan9d420872017-10-12 13:06:35 +0200194 self.dont_trace = False
195 self.manual_print = False
196 self.manual_endian = False
197 self.autoreply = False
198 self.singular = False
199 for f in flags:
Ole Troan2c2feab2018-04-24 00:02:37 -0400200 if f == 'dont_trace':
Ole Troan9d420872017-10-12 13:06:35 +0200201 self.dont_trace = True
202 elif f == 'manual_print':
203 self.manual_print = True
204 elif f == 'manual_endian':
205 self.manual_endian = True
206 elif f == 'autoreply':
207 self.autoreply = True
208
209 for b in block:
210 if isinstance(b, Option):
211 if b[1] == 'singular' and b[2] == 'true':
212 self.singular = True
213 block.remove(b)
214
215 def __repr__(self):
Vratko Polak7520e172019-08-01 10:31:49 +0200216 return self.name + str(self.flags) + str(self.block)
Ole Troan9d420872017-10-12 13:06:35 +0200217
218
219class Enum():
220 def __init__(self, name, block, enumtype='u32'):
221 self.name = name
222 self.enumtype = enumtype
Ole Troan2c2feab2018-04-24 00:02:37 -0400223
Ole Troan9d420872017-10-12 13:06:35 +0200224 count = 0
225 for i, b in enumerate(block):
226 if type(b) is list:
227 count = b[1]
228 else:
229 count += 1
230 block[i] = [b, count]
231
232 self.block = block
Ole Troan8dbfb432019-04-24 14:31:18 +0200233 self.crc = str(block).encode()
234 global_type_add(name, self)
Ole Troan9d420872017-10-12 13:06:35 +0200235
236 def __repr__(self):
Vratko Polak7520e172019-08-01 10:31:49 +0200237 return self.name + str(self.block)
Ole Troan9d420872017-10-12 13:06:35 +0200238
239
240class Import():
241 def __init__(self, filename):
242 self.filename = filename
243
244 # Deal with imports
245 parser = VPPAPI(filename=filename)
246 dirlist = dirlist_get()
247 f = filename
248 for dir in dirlist:
249 f = os.path.join(dir, filename)
250 if os.path.exists(f):
251 break
Ole Troan58914252018-10-23 10:50:07 +0200252 if sys.version[0] == '2':
253 with open(f) as fd:
254 self.result = parser.parse_file(fd, None)
255 else:
256 with open(f, encoding='utf-8') as fd:
257 self.result = parser.parse_file(fd, None)
Ole Troan9d420872017-10-12 13:06:35 +0200258
259 def __repr__(self):
Vratko Polak7520e172019-08-01 10:31:49 +0200260 return self.filename
Ole Troan9d420872017-10-12 13:06:35 +0200261
262
263class Option():
264 def __init__(self, option):
265 self.option = option
Ole Troan8dbfb432019-04-24 14:31:18 +0200266 self.crc = str(option).encode()
Ole Troan9d420872017-10-12 13:06:35 +0200267
268 def __repr__(self):
Vratko Polak7520e172019-08-01 10:31:49 +0200269 return str(self.option)
Ole Troan9d420872017-10-12 13:06:35 +0200270
271 def __getitem__(self, index):
272 return self.option[index]
273
274
275class Array():
276 def __init__(self, fieldtype, name, length):
277 self.type = 'Array'
278 self.fieldtype = fieldtype
279 self.fieldname = name
280 if type(length) is str:
281 self.lengthfield = length
282 self.length = 0
283 else:
284 self.length = length
285 self.lengthfield = None
286
287 def __repr__(self):
Vratko Polak7520e172019-08-01 10:31:49 +0200288 return str([self.fieldtype, self.fieldname, self.length,
289 self.lengthfield])
Ole Troan9d420872017-10-12 13:06:35 +0200290
291
292class Field():
Ole Troan9ac11382019-04-23 17:11:01 +0200293 def __init__(self, fieldtype, name, limit=None):
Ole Troan9d420872017-10-12 13:06:35 +0200294 self.type = 'Field'
295 self.fieldtype = fieldtype
296 self.fieldname = name
Ole Troan9ac11382019-04-23 17:11:01 +0200297 self.limit = limit
Ole Troan9d420872017-10-12 13:06:35 +0200298
299 def __repr__(self):
Vratko Polak7520e172019-08-01 10:31:49 +0200300 return str([self.fieldtype, self.fieldname])
Ole Troan9d420872017-10-12 13:06:35 +0200301
302
303class Coord(object):
304 """ Coordinates of a syntactic element. Consists of:
305 - File name
306 - Line number
307 - (optional) column number, for the Lexer
308 """
309 __slots__ = ('file', 'line', 'column', '__weakref__')
310
311 def __init__(self, file, line, column=None):
312 self.file = file
313 self.line = line
314 self.column = column
315
316 def __str__(self):
317 str = "%s:%s" % (self.file, self.line)
318 if self.column:
319 str += ":%s" % self.column
320 return str
321
322
323class ParseError(Exception):
324 pass
325
326
327#
328# Grammar rules
329#
330class VPPAPIParser(object):
331 tokens = VPPAPILexer.tokens
332
333 def __init__(self, filename, logger):
334 self.filename = filename
335 self.logger = logger
336 self.fields = []
337
338 def _parse_error(self, msg, coord):
339 raise ParseError("%s: %s" % (coord, msg))
340
341 def _parse_warning(self, msg, coord):
342 if self.logger:
343 self.logger.warning("%s: %s" % (coord, msg))
344
345 def _coord(self, lineno, column=None):
346 return Coord(
347 file=self.filename,
348 line=lineno, column=column)
349
350 def _token_coord(self, p, token_idx):
351 """ Returns the coordinates for the YaccProduction object 'p' indexed
352 with 'token_idx'. The coordinate includes the 'lineno' and
353 'column'. Both follow the lex semantic, starting from 1.
354 """
355 last_cr = p.lexer.lexdata.rfind('\n', 0, p.lexpos(token_idx))
356 if last_cr < 0:
357 last_cr = -1
358 column = (p.lexpos(token_idx) - (last_cr))
359 return self._coord(p.lineno(token_idx), column)
360
361 def p_slist(self, p):
362 '''slist : stmt
363 | slist stmt'''
364 if len(p) == 2:
365 p[0] = [p[1]]
366 else:
367 p[0] = p[1] + [p[2]]
368
369 def p_stmt(self, p):
370 '''stmt : define
371 | typedef
372 | option
373 | import
374 | enum
Ole Troan2c2feab2018-04-24 00:02:37 -0400375 | union
Ole Troan9d420872017-10-12 13:06:35 +0200376 | service'''
377 p[0] = p[1]
378
379 def p_import(self, p):
380 '''import : IMPORT STRING_LITERAL ';' '''
381 p[0] = Import(p[2])
382
383 def p_service(self, p):
384 '''service : SERVICE '{' service_statements '}' ';' '''
385 p[0] = p[3]
386
387 def p_service_statements(self, p):
388 '''service_statements : service_statement
389 | service_statements service_statement'''
390 if len(p) == 2:
391 p[0] = [p[1]]
392 else:
393 p[0] = p[1] + [p[2]]
394
395 def p_service_statement(self, p):
Marek Gradzki51e59682018-03-06 10:05:44 +0100396 '''service_statement : RPC ID RETURNS NULL ';'
397 | RPC ID RETURNS ID ';'
Ole Troan9d420872017-10-12 13:06:35 +0200398 | RPC ID RETURNS STREAM ID ';'
399 | RPC ID RETURNS ID EVENTS event_list ';' '''
Marek Gradzkifc70e3a2018-03-06 10:56:26 +0100400 if p[2] == p[4]:
401 # Verify that caller and reply differ
Ole Troan17225df2018-04-11 09:50:03 +0200402 self._parse_error(
403 'Reply ID ({}) should not be equal to Caller ID'.format(p[2]),
404 self._token_coord(p, 1))
Ole Troan9d420872017-10-12 13:06:35 +0200405 if len(p) == 8:
406 p[0] = Service(p[2], p[4], p[6])
407 elif len(p) == 7:
408 p[0] = Service(p[2], p[5], stream=True)
409 else:
410 p[0] = Service(p[2], p[4])
411
412 def p_event_list(self, p):
413 '''event_list : events
414 | event_list events '''
415 if len(p) == 2:
416 p[0] = [p[1]]
417 else:
418 p[0] = p[1] + [p[2]]
419
420 def p_event(self, p):
421 '''events : ID
422 | ID ',' '''
423 p[0] = p[1]
424
425 def p_enum(self, p):
426 '''enum : ENUM ID '{' enum_statements '}' ';' '''
427 p[0] = Enum(p[2], p[4])
428
429 def p_enum_type(self, p):
430 ''' enum : ENUM ID ':' enum_size '{' enum_statements '}' ';' '''
431 if len(p) == 9:
432 p[0] = Enum(p[2], p[6], enumtype=p[4])
433 else:
434 p[0] = Enum(p[2], p[4])
435
436 def p_enum_size(self, p):
437 ''' enum_size : U8
438 | U16
439 | U32 '''
440 p[0] = p[1]
441
442 def p_define(self, p):
443 '''define : DEFINE ID '{' block_statements_opt '}' ';' '''
444 self.fields = []
445 p[0] = Define(p[2], [], p[4])
446
447 def p_define_flist(self, p):
448 '''define : flist DEFINE ID '{' block_statements_opt '}' ';' '''
Ole Troan2c2feab2018-04-24 00:02:37 -0400449 # Legacy typedef
450 if 'typeonly' in p[1]:
451 p[0] = Typedef(p[3], p[1], p[5])
452 else:
453 p[0] = Define(p[3], p[1], p[5])
Ole Troan9d420872017-10-12 13:06:35 +0200454
455 def p_flist(self, p):
456 '''flist : flag
457 | flist flag'''
458 if len(p) == 2:
459 p[0] = [p[1]]
460 else:
461 p[0] = p[1] + [p[2]]
462
463 def p_flag(self, p):
464 '''flag : MANUAL_PRINT
465 | MANUAL_ENDIAN
466 | DONT_TRACE
467 | TYPEONLY
468 | AUTOREPLY'''
469 if len(p) == 1:
470 return
471 p[0] = p[1]
472
473 def p_typedef(self, p):
474 '''typedef : TYPEDEF ID '{' block_statements_opt '}' ';' '''
475 p[0] = Typedef(p[2], [], p[4])
476
Ole Troan53fffa12018-11-13 12:36:56 +0100477 def p_typedef_alias(self, p):
478 '''typedef : TYPEDEF declaration '''
479 p[0] = Using(p[2].fieldname, p[2])
480
Ole Troan9d420872017-10-12 13:06:35 +0200481 def p_block_statements_opt(self, p):
Ole Troan2c2feab2018-04-24 00:02:37 -0400482 '''block_statements_opt : block_statements '''
Ole Troan9d420872017-10-12 13:06:35 +0200483 p[0] = p[1]
484
485 def p_block_statements(self, p):
486 '''block_statements : block_statement
487 | block_statements block_statement'''
488 if len(p) == 2:
489 p[0] = [p[1]]
490 else:
491 p[0] = p[1] + [p[2]]
492
493 def p_block_statement(self, p):
494 '''block_statement : declaration
495 | option '''
496 p[0] = p[1]
497
498 def p_enum_statements(self, p):
499 '''enum_statements : enum_statement
Ole Troan9ac11382019-04-23 17:11:01 +0200500 | enum_statements enum_statement'''
Ole Troan9d420872017-10-12 13:06:35 +0200501 if len(p) == 2:
502 p[0] = [p[1]]
503 else:
504 p[0] = p[1] + [p[2]]
505
506 def p_enum_statement(self, p):
507 '''enum_statement : ID '=' NUM ','
508 | ID ',' '''
509 if len(p) == 5:
510 p[0] = [p[1], p[3]]
511 else:
512 p[0] = p[1]
513
Ole Troan85465582019-04-30 10:04:36 +0200514 def p_field_options(self, p):
515 '''field_options : field_option
516 | field_options field_option'''
517 if len(p) == 2:
518 p[0] = p[1]
519 else:
Vratko Polak7520e172019-08-01 10:31:49 +0200520 p[0] = { **p[1], **p[2] }
Ole Troan85465582019-04-30 10:04:36 +0200521
522 def p_field_option(self, p):
523 '''field_option : ID '=' assignee ','
524 | ID '=' assignee
525 '''
Vratko Polak7520e172019-08-01 10:31:49 +0200526 p[0] = { p[1]: p[3] }
Ole Troan85465582019-04-30 10:04:36 +0200527
Ole Troan9d420872017-10-12 13:06:35 +0200528 def p_declaration(self, p):
Ole Troan9ac11382019-04-23 17:11:01 +0200529 '''declaration : type_specifier ID ';'
Ole Troan85465582019-04-30 10:04:36 +0200530 | type_specifier ID '[' field_options ']' ';' '''
531 if len(p) == 7:
532 p[0] = Field(p[1], p[2], p[4])
Ole Troan9ac11382019-04-23 17:11:01 +0200533 elif len(p) == 4:
534 p[0] = Field(p[1], p[2])
535 else:
Ole Troan9d420872017-10-12 13:06:35 +0200536 self._parse_error('ERROR')
537 self.fields.append(p[2])
Ole Troan9ac11382019-04-23 17:11:01 +0200538
Ole Troan9d420872017-10-12 13:06:35 +0200539 def p_declaration_array(self, p):
540 '''declaration : type_specifier ID '[' NUM ']' ';'
541 | type_specifier ID '[' ID ']' ';' '''
542 if len(p) != 7:
543 return self._parse_error(
544 'array: %s' % p.value,
545 self._coord(lineno=p.lineno))
546
547 # Make this error later
548 if type(p[4]) is int and p[4] == 0:
549 # XXX: Line number is wrong
550 self._parse_warning('Old Style VLA: {} {}[{}];'
551 .format(p[1], p[2], p[4]),
552 self._token_coord(p, 1))
553
554 if type(p[4]) is str and p[4] not in self.fields:
555 # Verify that length field exists
556 self._parse_error('Missing length field: {} {}[{}];'
557 .format(p[1], p[2], p[4]),
558 self._token_coord(p, 1))
559 p[0] = Array(p[1], p[2], p[4])
560
561 def p_option(self, p):
562 '''option : OPTION ID '=' assignee ';' '''
563 p[0] = Option([p[1], p[2], p[4]])
564
565 def p_assignee(self, p):
566 '''assignee : NUM
567 | TRUE
568 | FALSE
569 | STRING_LITERAL '''
570 p[0] = p[1]
571
572 def p_type_specifier(self, p):
573 '''type_specifier : U8
574 | U16
575 | U32
576 | U64
577 | I8
578 | I16
579 | I32
580 | I64
581 | F64
582 | BOOL
583 | STRING'''
584 p[0] = p[1]
585
586 # Do a second pass later to verify that user defined types are defined
587 def p_typedef_specifier(self, p):
588 '''type_specifier : ID '''
589 if p[1] not in global_types:
590 self._parse_error('Undefined type: {}'.format(p[1]),
591 self._token_coord(p, 1))
592 p[0] = p[1]
593
Ole Troan2c2feab2018-04-24 00:02:37 -0400594 def p_union(self, p):
595 '''union : UNION ID '{' block_statements_opt '}' ';' '''
596 p[0] = Union(p[2], p[4])
597
Ole Troan9d420872017-10-12 13:06:35 +0200598 # Error rule for syntax errors
599 def p_error(self, p):
600 if p:
601 self._parse_error(
602 'before: %s' % p.value,
603 self._coord(lineno=p.lineno))
604 else:
605 self._parse_error('At end of input', self.filename)
606
607
608class VPPAPI(object):
609
610 def __init__(self, debug=False, filename='', logger=None):
611 self.lexer = lex.lex(module=VPPAPILexer(filename), debug=debug)
612 self.parser = yacc.yacc(module=VPPAPIParser(filename, logger),
Ole Troand6743b12018-03-07 08:40:58 +0100613 write_tables=False, debug=debug)
Ole Troan9d420872017-10-12 13:06:35 +0200614 self.logger = logger
615
616 def parse_string(self, code, debug=0, lineno=1):
617 self.lexer.lineno = lineno
618 return self.parser.parse(code, lexer=self.lexer, debug=debug)
619
620 def parse_file(self, fd, debug=0):
621 data = fd.read()
622 return self.parse_string(data, debug=debug)
623
624 def autoreply_block(self, name):
625 block = [Field('u32', 'context'),
626 Field('i32', 'retval')]
627 return Define(name + '_reply', [], block)
628
629 def process(self, objs):
630 s = {}
Ole Troan2c2feab2018-04-24 00:02:37 -0400631 s['Option'] = {}
632 s['Define'] = []
633 s['Service'] = []
634 s['types'] = []
635 s['Import'] = []
Ole Troan53fffa12018-11-13 12:36:56 +0100636 s['Alias'] = {}
Ole Troan8dbfb432019-04-24 14:31:18 +0200637 crc = 0
Ole Troan9d420872017-10-12 13:06:35 +0200638 for o in objs:
Ole Troan2c2feab2018-04-24 00:02:37 -0400639 tname = o.__class__.__name__
Ole Troan8dbfb432019-04-24 14:31:18 +0200640 try:
641 crc = binascii.crc32(o.crc, crc)
642 except AttributeError:
643 pass
Ole Troan9d420872017-10-12 13:06:35 +0200644 if isinstance(o, Define):
Ole Troan2c2feab2018-04-24 00:02:37 -0400645 s[tname].append(o)
646 if o.autoreply:
647 s[tname].append(self.autoreply_block(o.name))
Ole Troan9d420872017-10-12 13:06:35 +0200648 elif isinstance(o, Option):
Ole Troan2c2feab2018-04-24 00:02:37 -0400649 s[tname][o[1]] = o[2]
Ole Troan9d420872017-10-12 13:06:35 +0200650 elif type(o) is list:
651 for o2 in o:
652 if isinstance(o2, Service):
Ole Troan2c2feab2018-04-24 00:02:37 -0400653 s['Service'].append(o2)
Ole Troan58914252018-10-23 10:50:07 +0200654 elif (isinstance(o, Enum) or
655 isinstance(o, Typedef) or
656 isinstance(o, Union)):
Ole Troan2c2feab2018-04-24 00:02:37 -0400657 s['types'].append(o)
Ole Troan53fffa12018-11-13 12:36:56 +0100658 elif isinstance(o, Using):
659 s['Alias'][o.name] = o.alias
Ole Troan2c2feab2018-04-24 00:02:37 -0400660 else:
661 if tname not in s:
Ole Troan58914252018-10-23 10:50:07 +0200662 raise ValueError('Unknown class type: {} {}'
663 .format(tname, o))
Ole Troan2c2feab2018-04-24 00:02:37 -0400664 s[tname].append(o)
Ole Troan9d420872017-10-12 13:06:35 +0200665
Ole Troan2c2feab2018-04-24 00:02:37 -0400666 msgs = {d.name: d for d in s['Define']}
667 svcs = {s.caller: s for s in s['Service']}
668 replies = {s.reply: s for s in s['Service']}
Marek Gradzki51e59682018-03-06 10:05:44 +0100669 seen_services = {}
Ole Troan9d420872017-10-12 13:06:35 +0200670
Ole Troan8dbfb432019-04-24 14:31:18 +0200671 s['file_crc'] = crc
672
Ole Troan9d420872017-10-12 13:06:35 +0200673 for service in svcs:
674 if service not in msgs:
Ole Troan17225df2018-04-11 09:50:03 +0200675 raise ValueError(
676 'Service definition refers to unknown message'
677 ' definition: {}'.format(service))
678 if svcs[service].reply != 'null' and \
679 svcs[service].reply not in msgs:
Ole Troan9d420872017-10-12 13:06:35 +0200680 raise ValueError('Service definition refers to unknown message'
681 ' definition in reply: {}'
682 .format(svcs[service].reply))
Marek Gradzkib533f3f2018-03-06 11:10:56 +0100683 if service in replies:
684 raise ValueError('Service definition refers to message'
685 ' marked as reply: {}'.format(service))
Ole Troan9d420872017-10-12 13:06:35 +0200686 for event in svcs[service].events:
687 if event not in msgs:
688 raise ValueError('Service definition refers to unknown '
689 'event: {} in message: {}'
690 .format(event, service))
Marek Gradzki51e59682018-03-06 10:05:44 +0100691 seen_services[event] = True
Ole Troan9d420872017-10-12 13:06:35 +0200692
Marek Gradzki51e59682018-03-06 10:05:44 +0100693 # Create services implicitly
Ole Troan9d420872017-10-12 13:06:35 +0200694 for d in msgs:
Marek Gradzki51e59682018-03-06 10:05:44 +0100695 if d in seen_services:
696 continue
Ole Troan9d420872017-10-12 13:06:35 +0200697 if msgs[d].singular is True:
698 continue
Ole Troan9d420872017-10-12 13:06:35 +0200699 if d.endswith('_reply'):
700 if d[:-6] in svcs:
701 continue
702 if d[:-6] not in msgs:
Marek Gradzkicc134712018-03-06 12:25:02 +0100703 raise ValueError('{} missing calling message'
704 .format(d))
Ole Troan9d420872017-10-12 13:06:35 +0200705 continue
706 if d.endswith('_dump'):
707 if d in svcs:
708 continue
709 if d[:-5]+'_details' in msgs:
Ole Troan2c2feab2018-04-24 00:02:37 -0400710 s['Service'].append(Service(d, d[:-5]+'_details',
Ole Troan58914252018-10-23 10:50:07 +0200711 stream=True))
Ole Troan9d420872017-10-12 13:06:35 +0200712 else:
Marek Gradzkicc134712018-03-06 12:25:02 +0100713 raise ValueError('{} missing details message'
714 .format(d))
Ole Troan9d420872017-10-12 13:06:35 +0200715 continue
716
717 if d.endswith('_details'):
718 if d[:-8]+'_dump' not in msgs:
Marek Gradzkicc134712018-03-06 12:25:02 +0100719 raise ValueError('{} missing dump message'
720 .format(d))
Ole Troan9d420872017-10-12 13:06:35 +0200721 continue
722
723 if d in svcs:
724 continue
725 if d+'_reply' in msgs:
Ole Troan2c2feab2018-04-24 00:02:37 -0400726 s['Service'].append(Service(d, d+'_reply'))
Ole Troan9d420872017-10-12 13:06:35 +0200727 else:
Ole Troan17225df2018-04-11 09:50:03 +0200728 raise ValueError(
729 '{} missing reply message ({}) or service definition'
730 .format(d, d+'_reply'))
Ole Troan9d420872017-10-12 13:06:35 +0200731
732 return s
733
Ole Troan2c2feab2018-04-24 00:02:37 -0400734 def process_imports(self, objs, in_import, result):
Marek Gradzki51e59682018-03-06 10:05:44 +0100735 imported_objs = []
Ole Troan9d420872017-10-12 13:06:35 +0200736 for o in objs:
Ole Troan2c2feab2018-04-24 00:02:37 -0400737 # Only allow the following object types from imported file
738 if in_import and not (isinstance(o, Enum) or
739 isinstance(o, Union) or
Ole Troan10a09892018-06-29 11:32:33 +0200740 isinstance(o, Typedef) or
Ole Troan53fffa12018-11-13 12:36:56 +0100741 isinstance(o, Import) or
742 isinstance(o, Using)):
Ole Troan2c2feab2018-04-24 00:02:37 -0400743 continue
Ole Troan2c2feab2018-04-24 00:02:37 -0400744 if isinstance(o, Import):
745 self.process_imports(o.result, True, result)
Ole Troan10a09892018-06-29 11:32:33 +0200746 else:
747 result.append(o)
Ole Troan9d420872017-10-12 13:06:35 +0200748
Ole Troan58914252018-10-23 10:50:07 +0200749
Ole Troan9d420872017-10-12 13:06:35 +0200750# Add message ids to each message.
751def add_msg_id(s):
752 for o in s:
753 o.block.insert(0, Field('u16', '_vl_msg_id'))
754 return s
755
756
Ole Troan9d420872017-10-12 13:06:35 +0200757dirlist = []
758
759
760def dirlist_add(dirs):
761 global dirlist
762 if dirs:
763 dirlist = dirlist + dirs
764
765
766def dirlist_get():
767 return dirlist
768
Ole Troan8dbfb432019-04-24 14:31:18 +0200769def foldup_blocks(block, crc):
770 for b in block:
771 # Look up CRC in user defined types
772 if b.fieldtype.startswith('vl_api_'):
773 # Recursively
774 t = global_types[b.fieldtype]
775 try:
776 crc = crc_block_combine(t.block, crc)
777 return foldup_blocks(t.block, crc)
778 except:
779 pass
780 return crc
781
782def foldup_crcs(s):
783 for f in s:
784 f.crc = foldup_blocks(f.block,
785 binascii.crc32(f.crc))
Ole Troan9d420872017-10-12 13:06:35 +0200786
787#
788# Main
789#
790def main():
Ole Troan9d420872017-10-12 13:06:35 +0200791 cliparser = argparse.ArgumentParser(description='VPP API generator')
792 cliparser.add_argument('--pluginpath', default=""),
793 cliparser.add_argument('--includedir', action='append'),
Ole Troan58914252018-10-23 10:50:07 +0200794 if sys.version[0] == '2':
795 cliparser.add_argument('--input', type=argparse.FileType('r'),
796 default=sys.stdin)
Paul Vinciguerra7e0c48e2019-02-01 19:37:45 -0800797 cliparser.add_argument('--output', nargs='?',
798 type=argparse.FileType('w'),
799 default=sys.stdout)
800
Ole Troan58914252018-10-23 10:50:07 +0200801 else:
802 cliparser.add_argument('--input',
803 type=argparse.FileType('r', encoding='UTF-8'),
804 default=sys.stdin)
Paul Vinciguerra7e0c48e2019-02-01 19:37:45 -0800805 cliparser.add_argument('--output', nargs='?',
806 type=argparse.FileType('w', encoding='UTF-8'),
807 default=sys.stdout)
Ole Troan9d420872017-10-12 13:06:35 +0200808
809 cliparser.add_argument('output_module', nargs='?', default='C')
810 cliparser.add_argument('--debug', action='store_true')
811 cliparser.add_argument('--show-name', nargs=1)
812 args = cliparser.parse_args()
813
814 dirlist_add(args.includedir)
815 if not args.debug:
816 sys.excepthook = exception_handler
817
818 # Filename
819 if args.show_name:
820 filename = args.show_name[0]
821 elif args.input != sys.stdin:
822 filename = args.input.name
823 else:
824 filename = ''
825
Marek Gradzki51e59682018-03-06 10:05:44 +0100826 if args.debug:
827 logging.basicConfig(stream=sys.stdout, level=logging.WARNING)
828 else:
829 logging.basicConfig()
830 log = logging.getLogger('vppapigen')
831
Ole Troan9d420872017-10-12 13:06:35 +0200832 parser = VPPAPI(debug=args.debug, filename=filename, logger=log)
Ole Troan2c2feab2018-04-24 00:02:37 -0400833 parsed_objects = parser.parse_file(args.input, log)
Ole Troan9d420872017-10-12 13:06:35 +0200834
835 # Build a list of objects. Hash of lists.
Ole Troan2c2feab2018-04-24 00:02:37 -0400836 result = []
837 parser.process_imports(parsed_objects, False, result)
Ole Troan9d420872017-10-12 13:06:35 +0200838 s = parser.process(result)
839
840 # Add msg_id field
Ole Troan2c2feab2018-04-24 00:02:37 -0400841 s['Define'] = add_msg_id(s['Define'])
Ole Troan9d420872017-10-12 13:06:35 +0200842
Ole Troan8dbfb432019-04-24 14:31:18 +0200843 # Fold up CRCs
844 foldup_crcs(s['Define'])
Ole Troan9d420872017-10-12 13:06:35 +0200845
846 #
847 # Debug
848 if args.debug:
849 import pprint
Ole Troan10a09892018-06-29 11:32:33 +0200850 pp = pprint.PrettyPrinter(indent=4, stream=sys.stderr)
Ole Troan2c2feab2018-04-24 00:02:37 -0400851 for t in s['Define']:
Ole Troan9d420872017-10-12 13:06:35 +0200852 pp.pprint([t.name, t.flags, t.block])
Ole Troan2c2feab2018-04-24 00:02:37 -0400853 for t in s['types']:
854 pp.pprint([t.name, t.block])
Ole Troan9d420872017-10-12 13:06:35 +0200855
856 #
857 # Generate representation
858 #
Paul Vinciguerraf4647ed2019-02-12 12:21:01 -0800859 from importlib.machinery import SourceFileLoader
Ole Troan9d420872017-10-12 13:06:35 +0200860
861 # Default path
Ole Troan30787372018-03-01 13:33:39 +0100862 pluginpath = ''
Ole Troan9d420872017-10-12 13:06:35 +0200863 if not args.pluginpath:
Ole Troan30787372018-03-01 13:33:39 +0100864 cand = []
865 cand.append(os.path.dirname(os.path.realpath(__file__)))
Ole Troan17225df2018-04-11 09:50:03 +0200866 cand.append(os.path.dirname(os.path.realpath(__file__)) +
Ole Troan30787372018-03-01 13:33:39 +0100867 '/../share/vpp/')
868 for c in cand:
869 c += '/'
Ole Troan58914252018-10-23 10:50:07 +0200870 if os.path.isfile('{}vppapigen_{}.py'
871 .format(c, args.output_module.lower())):
Ole Troan30787372018-03-01 13:33:39 +0100872 pluginpath = c
873 break
Ole Troan9d420872017-10-12 13:06:35 +0200874 else:
875 pluginpath = args.pluginpath + '/'
Ole Troan30787372018-03-01 13:33:39 +0100876 if pluginpath == '':
877 raise Exception('Output plugin not found')
Ole Troan58914252018-10-23 10:50:07 +0200878 module_path = '{}vppapigen_{}.py'.format(pluginpath,
879 args.output_module.lower())
Ole Troan9d420872017-10-12 13:06:35 +0200880
881 try:
Paul Vinciguerraf4647ed2019-02-12 12:21:01 -0800882 plugin = SourceFileLoader(args.output_module,
883 module_path).load_module()
Ole Troan58914252018-10-23 10:50:07 +0200884 except Exception as err:
Ole Troan9d420872017-10-12 13:06:35 +0200885 raise Exception('Error importing output plugin: {}, {}'
886 .format(module_path, err))
887
Ole Troan8dbfb432019-04-24 14:31:18 +0200888 result = plugin.run(filename, s)
Ole Troan9d420872017-10-12 13:06:35 +0200889 if result:
Ole Troan17225df2018-04-11 09:50:03 +0200890 print(result, file=args.output)
Ole Troan9d420872017-10-12 13:06:35 +0200891 else:
892 raise Exception('Running plugin failed: {} {}'
893 .format(filename, result))
894
895
896if __name__ == '__main__':
897 main()