blob: b17ad6d15c99b305b53017e442d9e10f741fc5d0 [file] [log] [blame]
Nathan Skrzypczak5c2f9642019-09-09 16:45:06 +02001#!/usr/bin/env python3
Ole Troan9d420872017-10-12 13:06:35 +02002
Ole Troan9d420872017-10-12 13:06:35 +02003import ply.lex as lex
4import ply.yacc as yacc
5import sys
6import argparse
Paul Vinciguerraff47fb62019-08-06 19:58:24 -04007import keyword
Ole Troan9d420872017-10-12 13:06:35 +02008import logging
9import binascii
10import os
Ole Troan33a58172019-09-04 09:12:29 +020011import sys
Ole Troan9d420872017-10-12 13:06:35 +020012
Paul Vinciguerra2cd3cc82019-08-06 22:02:45 -040013log = logging.getLogger('vppapigen')
14
Ole Troand6743b12018-03-07 08:40:58 +010015# Ensure we don't leave temporary files around
16sys.dont_write_bytecode = True
17
Ole Troan9d420872017-10-12 13:06:35 +020018#
19# VPP API language
20#
21
22# Global dictionary of new types (including enums)
23global_types = {}
24
Paul Vinciguerra4bf84902019-07-31 00:34:05 -040025seen_imports = {}
26
Ole Troan9d420872017-10-12 13:06:35 +020027
Ole Troan8dbfb432019-04-24 14:31:18 +020028def global_type_add(name, obj):
Ole Troan9d420872017-10-12 13:06:35 +020029 '''Add new type to the dictionary of types '''
30 type_name = 'vl_api_' + name + '_t'
Paul Vinciguerra4bf84902019-07-31 00:34:05 -040031 if type_name in global_types:
32 raise KeyError("Attempted redefinition of {!r} with {!r}.".format(
33 name, obj))
Ole Troan8dbfb432019-04-24 14:31:18 +020034 global_types[type_name] = obj
Ole Troan9d420872017-10-12 13:06:35 +020035
36
37# All your trace are belong to us!
38def exception_handler(exception_type, exception, traceback):
Ole Troan17225df2018-04-11 09:50:03 +020039 print("%s: %s" % (exception_type.__name__, exception))
Ole Troan9d420872017-10-12 13:06:35 +020040
41
42#
43# Lexer
44#
45class VPPAPILexer(object):
46 def __init__(self, filename):
47 self.filename = filename
48
49 reserved = {
50 'service': 'SERVICE',
51 'rpc': 'RPC',
52 'returns': 'RETURNS',
Marek Gradzki51e59682018-03-06 10:05:44 +010053 'null': 'NULL',
Ole Troan9d420872017-10-12 13:06:35 +020054 'stream': 'STREAM',
55 'events': 'EVENTS',
56 'define': 'DEFINE',
57 'typedef': 'TYPEDEF',
58 'enum': 'ENUM',
59 'typeonly': 'TYPEONLY',
60 'manual_print': 'MANUAL_PRINT',
61 'manual_endian': 'MANUAL_ENDIAN',
62 'dont_trace': 'DONT_TRACE',
63 'autoreply': 'AUTOREPLY',
64 'option': 'OPTION',
65 'u8': 'U8',
66 'u16': 'U16',
67 'u32': 'U32',
68 'u64': 'U64',
69 'i8': 'I8',
70 'i16': 'I16',
71 'i32': 'I32',
72 'i64': 'I64',
73 'f64': 'F64',
74 'bool': 'BOOL',
75 'string': 'STRING',
76 'import': 'IMPORT',
77 'true': 'TRUE',
78 'false': 'FALSE',
Ole Troan2c2feab2018-04-24 00:02:37 -040079 'union': 'UNION',
Ole Troan9d420872017-10-12 13:06:35 +020080 }
81
82 tokens = ['STRING_LITERAL',
83 'ID', 'NUM'] + list(reserved.values())
84
85 t_ignore_LINE_COMMENT = '//.*'
86
Ole Troan33a58172019-09-04 09:12:29 +020087 def t_FALSE(self, t):
88 r'false'
89 t.value = False
90 return t
91
92 def t_TRUE(self, t):
93 r'false'
94 t.value = True
95 return t
96
Ole Troan9d420872017-10-12 13:06:35 +020097 def t_NUM(self, t):
Paul Vinciguerra063f3742019-07-02 13:00:58 -040098 r'0[xX][0-9a-fA-F]+|-?\d+\.?\d*'
Ole Troan9d420872017-10-12 13:06:35 +020099 base = 16 if t.value.startswith('0x') else 10
Paul Vinciguerra063f3742019-07-02 13:00:58 -0400100 if '.' in t.value:
101 t.value = float(t.value)
102 else:
103 t.value = int(t.value, base)
Ole Troan9d420872017-10-12 13:06:35 +0200104 return t
105
106 def t_ID(self, t):
107 r'[a-zA-Z_][a-zA-Z_0-9]*'
108 # Check for reserved words
109 t.type = VPPAPILexer.reserved.get(t.value, 'ID')
110 return t
111
112 # C string
113 def t_STRING_LITERAL(self, t):
114 r'\"([^\\\n]|(\\.))*?\"'
115 t.value = str(t.value).replace("\"", "")
116 return t
117
118 # C or C++ comment (ignore)
119 def t_comment(self, t):
120 r'(/\*(.|\n)*?\*/)|(//.*)'
121 t.lexer.lineno += t.value.count('\n')
122
123 # Error handling rule
124 def t_error(self, t):
125 raise ParseError("Illegal character '{}' ({})"
126 "in {}: line {}".format(t.value[0],
127 hex(ord(t.value[0])),
128 self.filename,
129 t.lexer.lineno))
130 t.lexer.skip(1)
131
132 # Define a rule so we can track line numbers
133 def t_newline(self, t):
134 r'\n+'
135 t.lexer.lineno += len(t.value)
136
137 literals = ":{}[];=.,"
138
139 # A string containing ignored characters (spaces and tabs)
140 t_ignore = ' \t'
141
Ole Troan17225df2018-04-11 09:50:03 +0200142
Ole Troan8dbfb432019-04-24 14:31:18 +0200143def crc_block_combine(block, crc):
Ole Troan58914252018-10-23 10:50:07 +0200144 s = str(block).encode()
Ole Troan8dbfb432019-04-24 14:31:18 +0200145 return binascii.crc32(s, crc) & 0xffffffff
Ole Troan58914252018-10-23 10:50:07 +0200146
Paul Vinciguerra063f3742019-07-02 13:00:58 -0400147
Ole Troand5a78a52019-09-18 12:12:47 +0200148def vla_is_last_check(name, block):
149 vla = False
150 for i, b in enumerate(block):
151 if isinstance(b, Array) and b.vla:
152 vla = True
153 if i + 1 < len(block):
154 raise ValueError(
155 'VLA field "{}" must be the last field in message "{}"'
156 .format(b.fieldname, name))
157 elif b.fieldtype.startswith('vl_api_'):
158 if global_types[b.fieldtype].vla:
159 vla = True
160 if i + 1 < len(block):
161 raise ValueError(
162 'VLA field "{}" must be the last '
163 'field in message "{}"'
164 .format(b.fieldname, name))
165 elif b.fieldtype == 'string' and b.length == 0:
166 vla = True
167 if i + 1 < len(block):
168 raise ValueError(
169 'VLA field "{}" must be the last '
170 'field in message "{}"'
171 .format(b.fieldname, name))
172 return vla
173
174
Ole Troan9d420872017-10-12 13:06:35 +0200175class Service():
Paul Vinciguerra7e0c48e2019-02-01 19:37:45 -0800176 def __init__(self, caller, reply, events=None, stream=False):
Ole Troan9d420872017-10-12 13:06:35 +0200177 self.caller = caller
178 self.reply = reply
179 self.stream = stream
Paul Vinciguerra7e0c48e2019-02-01 19:37:45 -0800180 self.events = [] if events is None else events
Ole Troan9d420872017-10-12 13:06:35 +0200181
182
183class Typedef():
184 def __init__(self, name, flags, block):
185 self.name = name
186 self.flags = flags
187 self.block = block
Ole Troan8dbfb432019-04-24 14:31:18 +0200188 self.crc = str(block).encode()
Ole Troan2c2feab2018-04-24 00:02:37 -0400189 self.manual_print = False
190 self.manual_endian = False
191 for f in flags:
192 if f == 'manual_print':
193 self.manual_print = True
194 elif f == 'manual_endian':
195 self.manual_endian = True
Ole Troan33a58172019-09-04 09:12:29 +0200196
Ole Troan8dbfb432019-04-24 14:31:18 +0200197 global_type_add(name, self)
Ole Troan9d420872017-10-12 13:06:35 +0200198
Ole Troand5a78a52019-09-18 12:12:47 +0200199 self.vla = vla_is_last_check(name, block)
Ole Troane5ff5a32019-08-23 22:55:18 +0200200
Ole Troan9d420872017-10-12 13:06:35 +0200201 def __repr__(self):
Vratko Polak7520e172019-08-01 10:31:49 +0200202 return self.name + str(self.flags) + str(self.block)
Ole Troan9d420872017-10-12 13:06:35 +0200203
204
Ole Troan53fffa12018-11-13 12:36:56 +0100205class Using():
Ole Troan33a58172019-09-04 09:12:29 +0200206 def __init__(self, name, flags, alias):
Ole Troan53fffa12018-11-13 12:36:56 +0100207 self.name = name
Ole Troane5ff5a32019-08-23 22:55:18 +0200208 self.vla = False
Ole Troan75761b92019-09-11 17:49:08 +0200209 self.block = []
210 self.manual_print = True
211 self.manual_endian = True
Ole Troan53fffa12018-11-13 12:36:56 +0100212
Ole Troan33a58172019-09-04 09:12:29 +0200213 self.manual_print = False
214 self.manual_endian = False
215 for f in flags:
216 if f == 'manual_print':
217 self.manual_print = True
218 elif f == 'manual_endian':
219 self.manual_endian = True
220
Ole Troan53fffa12018-11-13 12:36:56 +0100221 if isinstance(alias, Array):
Ole Troane5ff5a32019-08-23 22:55:18 +0200222 a = {'type': alias.fieldtype,
223 'length': alias.length}
Ole Troan53fffa12018-11-13 12:36:56 +0100224 else:
Ole Troane5ff5a32019-08-23 22:55:18 +0200225 a = {'type': alias.fieldtype}
Ole Troan53fffa12018-11-13 12:36:56 +0100226 self.alias = a
Ole Troan8dbfb432019-04-24 14:31:18 +0200227 self.crc = str(alias).encode()
228 global_type_add(name, self)
Ole Troan53fffa12018-11-13 12:36:56 +0100229
230 def __repr__(self):
Vratko Polak7520e172019-08-01 10:31:49 +0200231 return self.name + str(self.alias)
Ole Troan53fffa12018-11-13 12:36:56 +0100232
233
Ole Troan2c2feab2018-04-24 00:02:37 -0400234class Union():
Ole Troan33a58172019-09-04 09:12:29 +0200235 def __init__(self, name, flags, block):
Ole Troan2c2feab2018-04-24 00:02:37 -0400236 self.type = 'Union'
237 self.manual_print = False
238 self.manual_endian = False
Ole Troan2c2feab2018-04-24 00:02:37 -0400239 self.name = name
Ole Troan33a58172019-09-04 09:12:29 +0200240
Ole Troan33a58172019-09-04 09:12:29 +0200241 for f in flags:
242 if f == 'manual_print':
243 self.manual_print = True
244 elif f == 'manual_endian':
245 self.manual_endian = True
246
Ole Troan2c2feab2018-04-24 00:02:37 -0400247 self.block = block
Ole Troan8dbfb432019-04-24 14:31:18 +0200248 self.crc = str(block).encode()
Ole Troand5a78a52019-09-18 12:12:47 +0200249 self.vla = vla_is_last_check(name, block)
250
Ole Troan8dbfb432019-04-24 14:31:18 +0200251 global_type_add(name, self)
Ole Troan2c2feab2018-04-24 00:02:37 -0400252
253 def __repr__(self):
Vratko Polak7520e172019-08-01 10:31:49 +0200254 return str(self.block)
Ole Troan2c2feab2018-04-24 00:02:37 -0400255
256
Ole Troan9d420872017-10-12 13:06:35 +0200257class Define():
258 def __init__(self, name, flags, block):
259 self.name = name
260 self.flags = flags
261 self.block = block
Ole Troan9d420872017-10-12 13:06:35 +0200262 self.dont_trace = False
263 self.manual_print = False
264 self.manual_endian = False
265 self.autoreply = False
266 self.singular = False
Ole Troan2a1ca782019-09-19 01:08:30 +0200267 self.options = {}
Ole Troan9d420872017-10-12 13:06:35 +0200268 for f in flags:
Ole Troan2c2feab2018-04-24 00:02:37 -0400269 if f == 'dont_trace':
Ole Troan9d420872017-10-12 13:06:35 +0200270 self.dont_trace = True
271 elif f == 'manual_print':
272 self.manual_print = True
273 elif f == 'manual_endian':
274 self.manual_endian = True
275 elif f == 'autoreply':
276 self.autoreply = True
277
Ole Troand5a78a52019-09-18 12:12:47 +0200278 for b in block:
Ole Troan9d420872017-10-12 13:06:35 +0200279 if isinstance(b, Option):
280 if b[1] == 'singular' and b[2] == 'true':
281 self.singular = True
Ole Troan2a1ca782019-09-19 01:08:30 +0200282 else:
283 self.options[b.option] = b.value
Ole Troan9d420872017-10-12 13:06:35 +0200284 block.remove(b)
Ole Troan2a1ca782019-09-19 01:08:30 +0200285
Ole Troand5a78a52019-09-18 12:12:47 +0200286 self.vla = vla_is_last_check(name, block)
Ole Troan2a1ca782019-09-19 01:08:30 +0200287 self.crc = str(block).encode()
Ole Troane5ff5a32019-08-23 22:55:18 +0200288
Ole Troan9d420872017-10-12 13:06:35 +0200289 def __repr__(self):
Vratko Polak7520e172019-08-01 10:31:49 +0200290 return self.name + str(self.flags) + str(self.block)
Ole Troan9d420872017-10-12 13:06:35 +0200291
292
293class Enum():
294 def __init__(self, name, block, enumtype='u32'):
295 self.name = name
296 self.enumtype = enumtype
Ole Troane5ff5a32019-08-23 22:55:18 +0200297 self.vla = False
Ole Troan2c2feab2018-04-24 00:02:37 -0400298
Ole Troan9d420872017-10-12 13:06:35 +0200299 count = 0
300 for i, b in enumerate(block):
301 if type(b) is list:
302 count = b[1]
303 else:
304 count += 1
305 block[i] = [b, count]
306
307 self.block = block
Ole Troan8dbfb432019-04-24 14:31:18 +0200308 self.crc = str(block).encode()
309 global_type_add(name, self)
Ole Troan9d420872017-10-12 13:06:35 +0200310
311 def __repr__(self):
Vratko Polak7520e172019-08-01 10:31:49 +0200312 return self.name + str(self.block)
Ole Troan9d420872017-10-12 13:06:35 +0200313
314
315class Import():
Paul Vinciguerra4bf84902019-07-31 00:34:05 -0400316
317 def __new__(cls, *args, **kwargs):
318 if args[0] not in seen_imports:
319 instance = super().__new__(cls)
320 instance._initialized = False
321 seen_imports[args[0]] = instance
322
323 return seen_imports[args[0]]
324
Ole Troan9d420872017-10-12 13:06:35 +0200325 def __init__(self, filename):
Paul Vinciguerra4bf84902019-07-31 00:34:05 -0400326 if self._initialized:
327 return
328 else:
329 self.filename = filename
330 # Deal with imports
331 parser = VPPAPI(filename=filename)
332 dirlist = dirlist_get()
333 f = filename
334 for dir in dirlist:
335 f = os.path.join(dir, filename)
336 if os.path.exists(f):
337 break
338 if sys.version[0] == '2':
339 with open(f) as fd:
340 self.result = parser.parse_file(fd, None)
341 else:
342 with open(f, encoding='utf-8') as fd:
343 self.result = parser.parse_file(fd, None)
344 self._initialized = True
Ole Troan9d420872017-10-12 13:06:35 +0200345
346 def __repr__(self):
Vratko Polak7520e172019-08-01 10:31:49 +0200347 return self.filename
Ole Troan9d420872017-10-12 13:06:35 +0200348
349
350class Option():
Ole Troan33a58172019-09-04 09:12:29 +0200351 def __init__(self, option, value):
352 self.type = 'Option'
Ole Troan9d420872017-10-12 13:06:35 +0200353 self.option = option
Ole Troan33a58172019-09-04 09:12:29 +0200354 self.value = value
Ole Troan8dbfb432019-04-24 14:31:18 +0200355 self.crc = str(option).encode()
Ole Troan9d420872017-10-12 13:06:35 +0200356
357 def __repr__(self):
Vratko Polak7520e172019-08-01 10:31:49 +0200358 return str(self.option)
Ole Troan9d420872017-10-12 13:06:35 +0200359
360 def __getitem__(self, index):
361 return self.option[index]
362
363
364class Array():
Ole Troane5ff5a32019-08-23 22:55:18 +0200365 def __init__(self, fieldtype, name, length, modern_vla=False):
Ole Troan9d420872017-10-12 13:06:35 +0200366 self.type = 'Array'
367 self.fieldtype = fieldtype
368 self.fieldname = name
Ole Troane5ff5a32019-08-23 22:55:18 +0200369 self.modern_vla = modern_vla
Ole Troan9d420872017-10-12 13:06:35 +0200370 if type(length) is str:
371 self.lengthfield = length
372 self.length = 0
Ole Troane5ff5a32019-08-23 22:55:18 +0200373 self.vla = True
Ole Troan9d420872017-10-12 13:06:35 +0200374 else:
375 self.length = length
376 self.lengthfield = None
Ole Troane5ff5a32019-08-23 22:55:18 +0200377 self.vla = False
Ole Troan9d420872017-10-12 13:06:35 +0200378
379 def __repr__(self):
Vratko Polak7520e172019-08-01 10:31:49 +0200380 return str([self.fieldtype, self.fieldname, self.length,
381 self.lengthfield])
Ole Troan9d420872017-10-12 13:06:35 +0200382
383
384class Field():
Ole Troan9ac11382019-04-23 17:11:01 +0200385 def __init__(self, fieldtype, name, limit=None):
Ole Troan9d420872017-10-12 13:06:35 +0200386 self.type = 'Field'
387 self.fieldtype = fieldtype
Ole Troane5ff5a32019-08-23 22:55:18 +0200388
389 if self.fieldtype == 'string':
390 raise ValueError("The string type {!r} is an "
391 "array type ".format(name))
392
Paul Vinciguerraff47fb62019-08-06 19:58:24 -0400393 if name in keyword.kwlist:
394 raise ValueError("Fieldname {!r} is a python keyword and is not "
395 "accessible via the python API. ".format(name))
Ole Troan9d420872017-10-12 13:06:35 +0200396 self.fieldname = name
Ole Troan9ac11382019-04-23 17:11:01 +0200397 self.limit = limit
Ole Troan9d420872017-10-12 13:06:35 +0200398
399 def __repr__(self):
Vratko Polak7520e172019-08-01 10:31:49 +0200400 return str([self.fieldtype, self.fieldname])
Ole Troan9d420872017-10-12 13:06:35 +0200401
402
403class Coord(object):
404 """ Coordinates of a syntactic element. Consists of:
405 - File name
406 - Line number
407 - (optional) column number, for the Lexer
408 """
409 __slots__ = ('file', 'line', 'column', '__weakref__')
410
411 def __init__(self, file, line, column=None):
412 self.file = file
413 self.line = line
414 self.column = column
415
416 def __str__(self):
417 str = "%s:%s" % (self.file, self.line)
418 if self.column:
419 str += ":%s" % self.column
420 return str
421
422
423class ParseError(Exception):
424 pass
425
426
427#
428# Grammar rules
429#
430class VPPAPIParser(object):
431 tokens = VPPAPILexer.tokens
432
433 def __init__(self, filename, logger):
434 self.filename = filename
435 self.logger = logger
436 self.fields = []
437
438 def _parse_error(self, msg, coord):
439 raise ParseError("%s: %s" % (coord, msg))
440
441 def _parse_warning(self, msg, coord):
442 if self.logger:
443 self.logger.warning("%s: %s" % (coord, msg))
444
445 def _coord(self, lineno, column=None):
446 return Coord(
447 file=self.filename,
448 line=lineno, column=column)
449
450 def _token_coord(self, p, token_idx):
451 """ Returns the coordinates for the YaccProduction object 'p' indexed
452 with 'token_idx'. The coordinate includes the 'lineno' and
453 'column'. Both follow the lex semantic, starting from 1.
454 """
455 last_cr = p.lexer.lexdata.rfind('\n', 0, p.lexpos(token_idx))
456 if last_cr < 0:
457 last_cr = -1
458 column = (p.lexpos(token_idx) - (last_cr))
459 return self._coord(p.lineno(token_idx), column)
460
461 def p_slist(self, p):
462 '''slist : stmt
463 | slist stmt'''
464 if len(p) == 2:
465 p[0] = [p[1]]
466 else:
467 p[0] = p[1] + [p[2]]
468
469 def p_stmt(self, p):
470 '''stmt : define
471 | typedef
472 | option
473 | import
474 | enum
Ole Troan2c2feab2018-04-24 00:02:37 -0400475 | union
Ole Troan9d420872017-10-12 13:06:35 +0200476 | service'''
477 p[0] = p[1]
478
479 def p_import(self, p):
480 '''import : IMPORT STRING_LITERAL ';' '''
481 p[0] = Import(p[2])
482
483 def p_service(self, p):
484 '''service : SERVICE '{' service_statements '}' ';' '''
485 p[0] = p[3]
486
487 def p_service_statements(self, p):
488 '''service_statements : service_statement
489 | service_statements service_statement'''
490 if len(p) == 2:
491 p[0] = [p[1]]
492 else:
493 p[0] = p[1] + [p[2]]
494
495 def p_service_statement(self, p):
Marek Gradzki51e59682018-03-06 10:05:44 +0100496 '''service_statement : RPC ID RETURNS NULL ';'
497 | RPC ID RETURNS ID ';'
Ole Troan9d420872017-10-12 13:06:35 +0200498 | RPC ID RETURNS STREAM ID ';'
499 | RPC ID RETURNS ID EVENTS event_list ';' '''
Marek Gradzkifc70e3a2018-03-06 10:56:26 +0100500 if p[2] == p[4]:
501 # Verify that caller and reply differ
Ole Troan17225df2018-04-11 09:50:03 +0200502 self._parse_error(
503 'Reply ID ({}) should not be equal to Caller ID'.format(p[2]),
504 self._token_coord(p, 1))
Ole Troan9d420872017-10-12 13:06:35 +0200505 if len(p) == 8:
506 p[0] = Service(p[2], p[4], p[6])
507 elif len(p) == 7:
508 p[0] = Service(p[2], p[5], stream=True)
509 else:
510 p[0] = Service(p[2], p[4])
511
512 def p_event_list(self, p):
513 '''event_list : events
514 | event_list events '''
515 if len(p) == 2:
516 p[0] = [p[1]]
517 else:
518 p[0] = p[1] + [p[2]]
519
520 def p_event(self, p):
521 '''events : ID
522 | ID ',' '''
523 p[0] = p[1]
524
525 def p_enum(self, p):
526 '''enum : ENUM ID '{' enum_statements '}' ';' '''
527 p[0] = Enum(p[2], p[4])
528
529 def p_enum_type(self, p):
530 ''' enum : ENUM ID ':' enum_size '{' enum_statements '}' ';' '''
531 if len(p) == 9:
532 p[0] = Enum(p[2], p[6], enumtype=p[4])
533 else:
534 p[0] = Enum(p[2], p[4])
535
536 def p_enum_size(self, p):
537 ''' enum_size : U8
538 | U16
539 | U32 '''
540 p[0] = p[1]
541
542 def p_define(self, p):
543 '''define : DEFINE ID '{' block_statements_opt '}' ';' '''
544 self.fields = []
545 p[0] = Define(p[2], [], p[4])
546
547 def p_define_flist(self, p):
548 '''define : flist DEFINE ID '{' block_statements_opt '}' ';' '''
Ole Troan2c2feab2018-04-24 00:02:37 -0400549 # Legacy typedef
550 if 'typeonly' in p[1]:
Paul Vinciguerrae7174822019-08-07 00:05:59 -0400551 self._parse_error('legacy typedef. use typedef: {} {}[{}];'
552 .format(p[1], p[2], p[4]),
553 self._token_coord(p, 1))
Ole Troan2c2feab2018-04-24 00:02:37 -0400554 else:
555 p[0] = Define(p[3], p[1], p[5])
Ole Troan9d420872017-10-12 13:06:35 +0200556
557 def p_flist(self, p):
558 '''flist : flag
559 | flist flag'''
560 if len(p) == 2:
561 p[0] = [p[1]]
562 else:
563 p[0] = p[1] + [p[2]]
564
565 def p_flag(self, p):
566 '''flag : MANUAL_PRINT
567 | MANUAL_ENDIAN
568 | DONT_TRACE
569 | TYPEONLY
570 | AUTOREPLY'''
571 if len(p) == 1:
572 return
573 p[0] = p[1]
574
575 def p_typedef(self, p):
576 '''typedef : TYPEDEF ID '{' block_statements_opt '}' ';' '''
577 p[0] = Typedef(p[2], [], p[4])
578
Ole Troan33a58172019-09-04 09:12:29 +0200579 def p_typedef_flist(self, p):
580 '''typedef : flist TYPEDEF ID '{' block_statements_opt '}' ';' '''
581 p[0] = Typedef(p[3], p[1], p[5])
582
Ole Troan53fffa12018-11-13 12:36:56 +0100583 def p_typedef_alias(self, p):
584 '''typedef : TYPEDEF declaration '''
Ole Troan33a58172019-09-04 09:12:29 +0200585 p[0] = Using(p[2].fieldname, [], p[2])
586
587 def p_typedef_alias_flist(self, p):
588 '''typedef : flist TYPEDEF declaration '''
589 p[0] = Using(p[3].fieldname, p[1], p[3])
Ole Troan53fffa12018-11-13 12:36:56 +0100590
Ole Troan9d420872017-10-12 13:06:35 +0200591 def p_block_statements_opt(self, p):
Ole Troan2c2feab2018-04-24 00:02:37 -0400592 '''block_statements_opt : block_statements '''
Ole Troan9d420872017-10-12 13:06:35 +0200593 p[0] = p[1]
594
595 def p_block_statements(self, p):
596 '''block_statements : block_statement
597 | block_statements block_statement'''
598 if len(p) == 2:
599 p[0] = [p[1]]
600 else:
601 p[0] = p[1] + [p[2]]
602
603 def p_block_statement(self, p):
604 '''block_statement : declaration
605 | option '''
606 p[0] = p[1]
607
608 def p_enum_statements(self, p):
609 '''enum_statements : enum_statement
Ole Troan9ac11382019-04-23 17:11:01 +0200610 | enum_statements enum_statement'''
Ole Troan9d420872017-10-12 13:06:35 +0200611 if len(p) == 2:
612 p[0] = [p[1]]
613 else:
614 p[0] = p[1] + [p[2]]
615
616 def p_enum_statement(self, p):
617 '''enum_statement : ID '=' NUM ','
618 | ID ',' '''
619 if len(p) == 5:
620 p[0] = [p[1], p[3]]
621 else:
622 p[0] = p[1]
623
Ole Troan85465582019-04-30 10:04:36 +0200624 def p_field_options(self, p):
625 '''field_options : field_option
626 | field_options field_option'''
627 if len(p) == 2:
628 p[0] = p[1]
629 else:
Ole Troane5ff5a32019-08-23 22:55:18 +0200630 p[0] = {**p[1], **p[2]}
Ole Troan85465582019-04-30 10:04:36 +0200631
632 def p_field_option(self, p):
Ole Troane5ff5a32019-08-23 22:55:18 +0200633 '''field_option : ID
634 | ID '=' assignee ','
Ole Troan85465582019-04-30 10:04:36 +0200635 | ID '=' assignee
Ole Troane5ff5a32019-08-23 22:55:18 +0200636
Ole Troan85465582019-04-30 10:04:36 +0200637 '''
Ole Troane5ff5a32019-08-23 22:55:18 +0200638 if len(p) == 2:
639 p[0] = {p[1]: None}
640 else:
641 p[0] = {p[1]: p[3]}
Ole Troan85465582019-04-30 10:04:36 +0200642
Ole Troan9d420872017-10-12 13:06:35 +0200643 def p_declaration(self, p):
Ole Troan9ac11382019-04-23 17:11:01 +0200644 '''declaration : type_specifier ID ';'
Ole Troan85465582019-04-30 10:04:36 +0200645 | type_specifier ID '[' field_options ']' ';' '''
646 if len(p) == 7:
647 p[0] = Field(p[1], p[2], p[4])
Ole Troan9ac11382019-04-23 17:11:01 +0200648 elif len(p) == 4:
649 p[0] = Field(p[1], p[2])
650 else:
Paul Vinciguerra582eac52020-04-03 12:18:40 -0400651 self._parse_error('ERROR', self._token_coord(p, 1))
Ole Troan9d420872017-10-12 13:06:35 +0200652 self.fields.append(p[2])
Ole Troan9ac11382019-04-23 17:11:01 +0200653
Ole Troane5ff5a32019-08-23 22:55:18 +0200654 def p_declaration_array_vla(self, p):
655 '''declaration : type_specifier ID '[' ']' ';' '''
656 p[0] = Array(p[1], p[2], 0, modern_vla=True)
657
Ole Troan9d420872017-10-12 13:06:35 +0200658 def p_declaration_array(self, p):
659 '''declaration : type_specifier ID '[' NUM ']' ';'
660 | type_specifier ID '[' ID ']' ';' '''
Ole Troane5ff5a32019-08-23 22:55:18 +0200661
Ole Troan9d420872017-10-12 13:06:35 +0200662 if len(p) != 7:
663 return self._parse_error(
664 'array: %s' % p.value,
665 self._coord(lineno=p.lineno))
666
667 # Make this error later
668 if type(p[4]) is int and p[4] == 0:
669 # XXX: Line number is wrong
670 self._parse_warning('Old Style VLA: {} {}[{}];'
671 .format(p[1], p[2], p[4]),
672 self._token_coord(p, 1))
673
674 if type(p[4]) is str and p[4] not in self.fields:
675 # Verify that length field exists
676 self._parse_error('Missing length field: {} {}[{}];'
677 .format(p[1], p[2], p[4]),
678 self._token_coord(p, 1))
679 p[0] = Array(p[1], p[2], p[4])
680
681 def p_option(self, p):
682 '''option : OPTION ID '=' assignee ';' '''
Ole Troan33a58172019-09-04 09:12:29 +0200683 p[0] = Option(p[2], p[4])
Ole Troan9d420872017-10-12 13:06:35 +0200684
685 def p_assignee(self, p):
686 '''assignee : NUM
687 | TRUE
688 | FALSE
689 | STRING_LITERAL '''
690 p[0] = p[1]
691
692 def p_type_specifier(self, p):
693 '''type_specifier : U8
694 | U16
695 | U32
696 | U64
697 | I8
698 | I16
699 | I32
700 | I64
701 | F64
702 | BOOL
703 | STRING'''
704 p[0] = p[1]
705
706 # Do a second pass later to verify that user defined types are defined
707 def p_typedef_specifier(self, p):
708 '''type_specifier : ID '''
709 if p[1] not in global_types:
710 self._parse_error('Undefined type: {}'.format(p[1]),
711 self._token_coord(p, 1))
712 p[0] = p[1]
713
Ole Troan2c2feab2018-04-24 00:02:37 -0400714 def p_union(self, p):
715 '''union : UNION ID '{' block_statements_opt '}' ';' '''
Ole Troan33a58172019-09-04 09:12:29 +0200716 p[0] = Union(p[2], [], p[4])
717
718 def p_union_flist(self, p):
719 '''union : flist UNION ID '{' block_statements_opt '}' ';' '''
720 p[0] = Union(p[3], p[1], p[5])
Ole Troan2c2feab2018-04-24 00:02:37 -0400721
Ole Troan9d420872017-10-12 13:06:35 +0200722 # Error rule for syntax errors
723 def p_error(self, p):
724 if p:
725 self._parse_error(
726 'before: %s' % p.value,
727 self._coord(lineno=p.lineno))
728 else:
729 self._parse_error('At end of input', self.filename)
730
731
732class VPPAPI(object):
733
734 def __init__(self, debug=False, filename='', logger=None):
735 self.lexer = lex.lex(module=VPPAPILexer(filename), debug=debug)
736 self.parser = yacc.yacc(module=VPPAPIParser(filename, logger),
Ole Troand6743b12018-03-07 08:40:58 +0100737 write_tables=False, debug=debug)
Ole Troan9d420872017-10-12 13:06:35 +0200738 self.logger = logger
739
740 def parse_string(self, code, debug=0, lineno=1):
741 self.lexer.lineno = lineno
742 return self.parser.parse(code, lexer=self.lexer, debug=debug)
743
744 def parse_file(self, fd, debug=0):
745 data = fd.read()
746 return self.parse_string(data, debug=debug)
747
748 def autoreply_block(self, name):
749 block = [Field('u32', 'context'),
750 Field('i32', 'retval')]
751 return Define(name + '_reply', [], block)
752
753 def process(self, objs):
754 s = {}
Ole Troan2c2feab2018-04-24 00:02:37 -0400755 s['Option'] = {}
756 s['Define'] = []
757 s['Service'] = []
758 s['types'] = []
759 s['Import'] = []
Ole Troan8dbfb432019-04-24 14:31:18 +0200760 crc = 0
Ole Troan9d420872017-10-12 13:06:35 +0200761 for o in objs:
Ole Troan2c2feab2018-04-24 00:02:37 -0400762 tname = o.__class__.__name__
Ole Troan8dbfb432019-04-24 14:31:18 +0200763 try:
Mark Nelsonea2abba2020-03-04 15:32:09 -0500764 crc = binascii.crc32(o.crc, crc) & 0xffffffff
Ole Troan8dbfb432019-04-24 14:31:18 +0200765 except AttributeError:
766 pass
Ole Troan9d420872017-10-12 13:06:35 +0200767 if isinstance(o, Define):
Ole Troan2c2feab2018-04-24 00:02:37 -0400768 s[tname].append(o)
769 if o.autoreply:
770 s[tname].append(self.autoreply_block(o.name))
Ole Troan9d420872017-10-12 13:06:35 +0200771 elif isinstance(o, Option):
Ole Troan59b6c0c2020-02-04 09:12:00 +0100772 s[tname][o.option] = o.value
Ole Troan9d420872017-10-12 13:06:35 +0200773 elif type(o) is list:
774 for o2 in o:
775 if isinstance(o2, Service):
Ole Troan2c2feab2018-04-24 00:02:37 -0400776 s['Service'].append(o2)
Ole Troan58914252018-10-23 10:50:07 +0200777 elif (isinstance(o, Enum) or
778 isinstance(o, Typedef) or
Ole Troan75761b92019-09-11 17:49:08 +0200779 isinstance(o, Using) or
Ole Troan58914252018-10-23 10:50:07 +0200780 isinstance(o, Union)):
Ole Troan2c2feab2018-04-24 00:02:37 -0400781 s['types'].append(o)
782 else:
783 if tname not in s:
Ole Troan58914252018-10-23 10:50:07 +0200784 raise ValueError('Unknown class type: {} {}'
785 .format(tname, o))
Ole Troan2c2feab2018-04-24 00:02:37 -0400786 s[tname].append(o)
Ole Troan9d420872017-10-12 13:06:35 +0200787
Ole Troan2c2feab2018-04-24 00:02:37 -0400788 msgs = {d.name: d for d in s['Define']}
789 svcs = {s.caller: s for s in s['Service']}
790 replies = {s.reply: s for s in s['Service']}
Marek Gradzki51e59682018-03-06 10:05:44 +0100791 seen_services = {}
Ole Troan9d420872017-10-12 13:06:35 +0200792
Ole Troan8dbfb432019-04-24 14:31:18 +0200793 s['file_crc'] = crc
794
Ole Troan9d420872017-10-12 13:06:35 +0200795 for service in svcs:
796 if service not in msgs:
Ole Troan17225df2018-04-11 09:50:03 +0200797 raise ValueError(
798 'Service definition refers to unknown message'
799 ' definition: {}'.format(service))
800 if svcs[service].reply != 'null' and \
801 svcs[service].reply not in msgs:
Ole Troan9d420872017-10-12 13:06:35 +0200802 raise ValueError('Service definition refers to unknown message'
803 ' definition in reply: {}'
804 .format(svcs[service].reply))
Marek Gradzkib533f3f2018-03-06 11:10:56 +0100805 if service in replies:
806 raise ValueError('Service definition refers to message'
807 ' marked as reply: {}'.format(service))
Ole Troan9d420872017-10-12 13:06:35 +0200808 for event in svcs[service].events:
809 if event not in msgs:
810 raise ValueError('Service definition refers to unknown '
811 'event: {} in message: {}'
812 .format(event, service))
Marek Gradzki51e59682018-03-06 10:05:44 +0100813 seen_services[event] = True
Ole Troan9d420872017-10-12 13:06:35 +0200814
Marek Gradzki51e59682018-03-06 10:05:44 +0100815 # Create services implicitly
Ole Troan9d420872017-10-12 13:06:35 +0200816 for d in msgs:
Marek Gradzki51e59682018-03-06 10:05:44 +0100817 if d in seen_services:
818 continue
Ole Troan9d420872017-10-12 13:06:35 +0200819 if msgs[d].singular is True:
820 continue
Ole Troan9d420872017-10-12 13:06:35 +0200821 if d.endswith('_reply'):
822 if d[:-6] in svcs:
823 continue
824 if d[:-6] not in msgs:
Marek Gradzkicc134712018-03-06 12:25:02 +0100825 raise ValueError('{} missing calling message'
826 .format(d))
Ole Troan9d420872017-10-12 13:06:35 +0200827 continue
828 if d.endswith('_dump'):
829 if d in svcs:
830 continue
831 if d[:-5]+'_details' in msgs:
Ole Troan2c2feab2018-04-24 00:02:37 -0400832 s['Service'].append(Service(d, d[:-5]+'_details',
Ole Troan58914252018-10-23 10:50:07 +0200833 stream=True))
Ole Troan9d420872017-10-12 13:06:35 +0200834 else:
Marek Gradzkicc134712018-03-06 12:25:02 +0100835 raise ValueError('{} missing details message'
836 .format(d))
Ole Troan9d420872017-10-12 13:06:35 +0200837 continue
838
839 if d.endswith('_details'):
840 if d[:-8]+'_dump' not in msgs:
Marek Gradzkicc134712018-03-06 12:25:02 +0100841 raise ValueError('{} missing dump message'
842 .format(d))
Ole Troan9d420872017-10-12 13:06:35 +0200843 continue
844
845 if d in svcs:
846 continue
847 if d+'_reply' in msgs:
Ole Troan2c2feab2018-04-24 00:02:37 -0400848 s['Service'].append(Service(d, d+'_reply'))
Ole Troan9d420872017-10-12 13:06:35 +0200849 else:
Ole Troan17225df2018-04-11 09:50:03 +0200850 raise ValueError(
851 '{} missing reply message ({}) or service definition'
852 .format(d, d+'_reply'))
Ole Troan9d420872017-10-12 13:06:35 +0200853
854 return s
855
Ole Troan2c2feab2018-04-24 00:02:37 -0400856 def process_imports(self, objs, in_import, result):
Marek Gradzki51e59682018-03-06 10:05:44 +0100857 imported_objs = []
Ole Troan9d420872017-10-12 13:06:35 +0200858 for o in objs:
Ole Troan2c2feab2018-04-24 00:02:37 -0400859 # Only allow the following object types from imported file
860 if in_import and not (isinstance(o, Enum) or
861 isinstance(o, Union) or
Ole Troan10a09892018-06-29 11:32:33 +0200862 isinstance(o, Typedef) or
Ole Troan53fffa12018-11-13 12:36:56 +0100863 isinstance(o, Import) or
864 isinstance(o, Using)):
Ole Troan2c2feab2018-04-24 00:02:37 -0400865 continue
Ole Troan2c2feab2018-04-24 00:02:37 -0400866 if isinstance(o, Import):
Ole Troan33a58172019-09-04 09:12:29 +0200867 result.append(o)
Paul Vinciguerra4bf84902019-07-31 00:34:05 -0400868 result = self.process_imports(o.result, True, result)
Ole Troan10a09892018-06-29 11:32:33 +0200869 else:
870 result.append(o)
Paul Vinciguerra4bf84902019-07-31 00:34:05 -0400871 return result
Ole Troan9d420872017-10-12 13:06:35 +0200872
Ole Troan58914252018-10-23 10:50:07 +0200873
Ole Troan9d420872017-10-12 13:06:35 +0200874# Add message ids to each message.
875def add_msg_id(s):
876 for o in s:
877 o.block.insert(0, Field('u16', '_vl_msg_id'))
878 return s
879
880
Ole Troan9d420872017-10-12 13:06:35 +0200881dirlist = []
882
883
884def dirlist_add(dirs):
885 global dirlist
886 if dirs:
887 dirlist = dirlist + dirs
888
889
890def dirlist_get():
891 return dirlist
892
Paul Vinciguerra2cd3cc82019-08-06 22:02:45 -0400893
Ole Troan8dbfb432019-04-24 14:31:18 +0200894def foldup_blocks(block, crc):
895 for b in block:
896 # Look up CRC in user defined types
897 if b.fieldtype.startswith('vl_api_'):
898 # Recursively
899 t = global_types[b.fieldtype]
900 try:
901 crc = crc_block_combine(t.block, crc)
902 return foldup_blocks(t.block, crc)
Ole Troane5ff5a32019-08-23 22:55:18 +0200903 except AttributeError:
Ole Troan8dbfb432019-04-24 14:31:18 +0200904 pass
905 return crc
906
Paul Vinciguerra2cd3cc82019-08-06 22:02:45 -0400907
Ole Troan8dbfb432019-04-24 14:31:18 +0200908def foldup_crcs(s):
909 for f in s:
910 f.crc = foldup_blocks(f.block,
Mark Nelsonea2abba2020-03-04 15:32:09 -0500911 binascii.crc32(f.crc) & 0xffffffff)
Ole Troan9d420872017-10-12 13:06:35 +0200912
Paul Vinciguerra2cd3cc82019-08-06 22:02:45 -0400913
Ole Troan9d420872017-10-12 13:06:35 +0200914#
915# Main
916#
917def main():
Paul Vinciguerra2cd3cc82019-08-06 22:02:45 -0400918 if sys.version_info < (3, 5,):
919 log.exception('vppapigen requires a supported version of python. '
920 'Please use version 3.5 or greater. '
921 'Using {}'.format(sys.version))
922 return 1
923
Ole Troan9d420872017-10-12 13:06:35 +0200924 cliparser = argparse.ArgumentParser(description='VPP API generator')
925 cliparser.add_argument('--pluginpath', default=""),
926 cliparser.add_argument('--includedir', action='append'),
Ole Troan2a1ca782019-09-19 01:08:30 +0200927 cliparser.add_argument('--outputdir', action='store'),
Paul Vinciguerra2cd3cc82019-08-06 22:02:45 -0400928 cliparser.add_argument('--input',
929 type=argparse.FileType('r', encoding='UTF-8'),
930 default=sys.stdin)
931 cliparser.add_argument('--output', nargs='?',
932 type=argparse.FileType('w', encoding='UTF-8'),
933 default=sys.stdout)
Ole Troan9d420872017-10-12 13:06:35 +0200934
935 cliparser.add_argument('output_module', nargs='?', default='C')
936 cliparser.add_argument('--debug', action='store_true')
937 cliparser.add_argument('--show-name', nargs=1)
938 args = cliparser.parse_args()
939
940 dirlist_add(args.includedir)
941 if not args.debug:
942 sys.excepthook = exception_handler
943
944 # Filename
945 if args.show_name:
946 filename = args.show_name[0]
947 elif args.input != sys.stdin:
948 filename = args.input.name
949 else:
950 filename = ''
951
Marek Gradzki51e59682018-03-06 10:05:44 +0100952 if args.debug:
953 logging.basicConfig(stream=sys.stdout, level=logging.WARNING)
954 else:
955 logging.basicConfig()
Marek Gradzki51e59682018-03-06 10:05:44 +0100956
Ole Troan9d420872017-10-12 13:06:35 +0200957 parser = VPPAPI(debug=args.debug, filename=filename, logger=log)
Ole Troan2c2feab2018-04-24 00:02:37 -0400958 parsed_objects = parser.parse_file(args.input, log)
Ole Troan9d420872017-10-12 13:06:35 +0200959
960 # Build a list of objects. Hash of lists.
Ole Troan2c2feab2018-04-24 00:02:37 -0400961 result = []
Ole Troan33a58172019-09-04 09:12:29 +0200962
963 if args.output_module == 'C':
964 s = parser.process(parsed_objects)
965 else:
Paul Vinciguerra4bf84902019-07-31 00:34:05 -0400966 result = parser.process_imports(parsed_objects, False, result)
Ole Troan33a58172019-09-04 09:12:29 +0200967 s = parser.process(result)
Ole Troan9d420872017-10-12 13:06:35 +0200968
969 # Add msg_id field
Ole Troan2c2feab2018-04-24 00:02:37 -0400970 s['Define'] = add_msg_id(s['Define'])
Ole Troan9d420872017-10-12 13:06:35 +0200971
Ole Troan8dbfb432019-04-24 14:31:18 +0200972 # Fold up CRCs
973 foldup_crcs(s['Define'])
Ole Troan9d420872017-10-12 13:06:35 +0200974
975 #
976 # Debug
977 if args.debug:
978 import pprint
Ole Troan10a09892018-06-29 11:32:33 +0200979 pp = pprint.PrettyPrinter(indent=4, stream=sys.stderr)
Ole Troan2c2feab2018-04-24 00:02:37 -0400980 for t in s['Define']:
Ole Troan9d420872017-10-12 13:06:35 +0200981 pp.pprint([t.name, t.flags, t.block])
Ole Troan2c2feab2018-04-24 00:02:37 -0400982 for t in s['types']:
983 pp.pprint([t.name, t.block])
Ole Troan9d420872017-10-12 13:06:35 +0200984
985 #
986 # Generate representation
987 #
Paul Vinciguerraf4647ed2019-02-12 12:21:01 -0800988 from importlib.machinery import SourceFileLoader
Ole Troan9d420872017-10-12 13:06:35 +0200989
990 # Default path
Ole Troan30787372018-03-01 13:33:39 +0100991 pluginpath = ''
Ole Troan9d420872017-10-12 13:06:35 +0200992 if not args.pluginpath:
Ole Troan30787372018-03-01 13:33:39 +0100993 cand = []
994 cand.append(os.path.dirname(os.path.realpath(__file__)))
Ole Troan17225df2018-04-11 09:50:03 +0200995 cand.append(os.path.dirname(os.path.realpath(__file__)) +
Ole Troan30787372018-03-01 13:33:39 +0100996 '/../share/vpp/')
997 for c in cand:
998 c += '/'
Ole Troan58914252018-10-23 10:50:07 +0200999 if os.path.isfile('{}vppapigen_{}.py'
1000 .format(c, args.output_module.lower())):
Ole Troan30787372018-03-01 13:33:39 +01001001 pluginpath = c
1002 break
Ole Troan9d420872017-10-12 13:06:35 +02001003 else:
1004 pluginpath = args.pluginpath + '/'
Ole Troan30787372018-03-01 13:33:39 +01001005 if pluginpath == '':
Paul Vinciguerra2cd3cc82019-08-06 22:02:45 -04001006 log.exception('Output plugin not found')
1007 return 1
Ole Troan58914252018-10-23 10:50:07 +02001008 module_path = '{}vppapigen_{}.py'.format(pluginpath,
1009 args.output_module.lower())
Ole Troan9d420872017-10-12 13:06:35 +02001010
1011 try:
Paul Vinciguerraf4647ed2019-02-12 12:21:01 -08001012 plugin = SourceFileLoader(args.output_module,
1013 module_path).load_module()
Ole Troan58914252018-10-23 10:50:07 +02001014 except Exception as err:
Paul Vinciguerra2cd3cc82019-08-06 22:02:45 -04001015 log.exception('Error importing output plugin: {}, {}'
1016 .format(module_path, err))
1017 return 1
Ole Troan9d420872017-10-12 13:06:35 +02001018
Ole Troan2a1ca782019-09-19 01:08:30 +02001019 result = plugin.run(args, filename, s)
Ole Troan9d420872017-10-12 13:06:35 +02001020 if result:
Ole Troan17225df2018-04-11 09:50:03 +02001021 print(result, file=args.output)
Ole Troan9d420872017-10-12 13:06:35 +02001022 else:
Paul Vinciguerra2cd3cc82019-08-06 22:02:45 -04001023 log.exception('Running plugin failed: {} {}'
1024 .format(filename, result))
1025 return 1
1026 return 0
Ole Troan9d420872017-10-12 13:06:35 +02001027
1028
1029if __name__ == '__main__':
Paul Vinciguerra2cd3cc82019-08-06 22:02:45 -04001030 sys.exit(main())