blob: c2f221b9e793d40c268bbe1e06b7cd78a61af3d0 [file] [log] [blame]
Paul Vinciguerraf4647ed2019-02-12 12:21:01 -08001#!/usr/bin/python3
Ole Troan9d420872017-10-12 13:06:35 +02002
3from __future__ import print_function
4import ply.lex as lex
5import ply.yacc as yacc
6import sys
7import argparse
8import logging
9import binascii
10import os
11
Ole Troand6743b12018-03-07 08:40:58 +010012# Ensure we don't leave temporary files around
13sys.dont_write_bytecode = True
14
Ole Troan9d420872017-10-12 13:06:35 +020015#
16# VPP API language
17#
18
19# Global dictionary of new types (including enums)
20global_types = {}
Ole Troan17225df2018-04-11 09:50:03 +020021global_crc = 0
Ole Troan9d420872017-10-12 13:06:35 +020022
23
24def global_type_add(name):
25 '''Add new type to the dictionary of types '''
26 type_name = 'vl_api_' + name + '_t'
27 if type_name in global_types:
28 raise KeyError('Type is already defined: {}'.format(name))
29 global_types[type_name] = True
30
31
32# All your trace are belong to us!
33def exception_handler(exception_type, exception, traceback):
Ole Troan17225df2018-04-11 09:50:03 +020034 print("%s: %s" % (exception_type.__name__, exception))
Ole Troan9d420872017-10-12 13:06:35 +020035
36
37#
38# Lexer
39#
40class VPPAPILexer(object):
41 def __init__(self, filename):
42 self.filename = filename
43
44 reserved = {
45 'service': 'SERVICE',
46 'rpc': 'RPC',
47 'returns': 'RETURNS',
Marek Gradzki51e59682018-03-06 10:05:44 +010048 'null': 'NULL',
Ole Troan9d420872017-10-12 13:06:35 +020049 'stream': 'STREAM',
50 'events': 'EVENTS',
51 'define': 'DEFINE',
52 'typedef': 'TYPEDEF',
53 'enum': 'ENUM',
54 'typeonly': 'TYPEONLY',
55 'manual_print': 'MANUAL_PRINT',
56 'manual_endian': 'MANUAL_ENDIAN',
57 'dont_trace': 'DONT_TRACE',
58 'autoreply': 'AUTOREPLY',
59 'option': 'OPTION',
60 'u8': 'U8',
61 'u16': 'U16',
62 'u32': 'U32',
63 'u64': 'U64',
64 'i8': 'I8',
65 'i16': 'I16',
66 'i32': 'I32',
67 'i64': 'I64',
68 'f64': 'F64',
69 'bool': 'BOOL',
70 'string': 'STRING',
71 'import': 'IMPORT',
72 'true': 'TRUE',
73 'false': 'FALSE',
Ole Troan2c2feab2018-04-24 00:02:37 -040074 'union': 'UNION',
Ole Troan9d420872017-10-12 13:06:35 +020075 }
76
77 tokens = ['STRING_LITERAL',
78 'ID', 'NUM'] + list(reserved.values())
79
80 t_ignore_LINE_COMMENT = '//.*'
81
82 def t_NUM(self, t):
83 r'0[xX][0-9a-fA-F]+|\d+'
84 base = 16 if t.value.startswith('0x') else 10
85 t.value = int(t.value, base)
86 return t
87
88 def t_ID(self, t):
89 r'[a-zA-Z_][a-zA-Z_0-9]*'
90 # Check for reserved words
91 t.type = VPPAPILexer.reserved.get(t.value, 'ID')
92 return t
93
94 # C string
95 def t_STRING_LITERAL(self, t):
96 r'\"([^\\\n]|(\\.))*?\"'
97 t.value = str(t.value).replace("\"", "")
98 return t
99
100 # C or C++ comment (ignore)
101 def t_comment(self, t):
102 r'(/\*(.|\n)*?\*/)|(//.*)'
103 t.lexer.lineno += t.value.count('\n')
104
105 # Error handling rule
106 def t_error(self, t):
107 raise ParseError("Illegal character '{}' ({})"
108 "in {}: line {}".format(t.value[0],
109 hex(ord(t.value[0])),
110 self.filename,
111 t.lexer.lineno))
112 t.lexer.skip(1)
113
114 # Define a rule so we can track line numbers
115 def t_newline(self, t):
116 r'\n+'
117 t.lexer.lineno += len(t.value)
118
119 literals = ":{}[];=.,"
120
121 # A string containing ignored characters (spaces and tabs)
122 t_ignore = ' \t'
123
Ole Troan17225df2018-04-11 09:50:03 +0200124
Ole Troan58914252018-10-23 10:50:07 +0200125#
126# Side-effect: Sets global_crc
127#
128def crc_block(block):
129 global global_crc
130 s = str(block).encode()
131 global_crc = binascii.crc32(s, global_crc)
132 return binascii.crc32(s) & 0xffffffff
133
134
Ole Troan9d420872017-10-12 13:06:35 +0200135class Service():
Paul Vinciguerra7e0c48e2019-02-01 19:37:45 -0800136 def __init__(self, caller, reply, events=None, stream=False):
Ole Troan9d420872017-10-12 13:06:35 +0200137 self.caller = caller
138 self.reply = reply
139 self.stream = stream
Paul Vinciguerra7e0c48e2019-02-01 19:37:45 -0800140 self.events = [] if events is None else events
Ole Troan9d420872017-10-12 13:06:35 +0200141
142
143class Typedef():
144 def __init__(self, name, flags, block):
145 self.name = name
146 self.flags = flags
147 self.block = block
Ole Troan58914252018-10-23 10:50:07 +0200148 self.crc = crc_block(block)
Ole Troan2c2feab2018-04-24 00:02:37 -0400149 self.manual_print = False
150 self.manual_endian = False
151 for f in flags:
152 if f == 'manual_print':
153 self.manual_print = True
154 elif f == 'manual_endian':
155 self.manual_endian = True
Ole Troan9d420872017-10-12 13:06:35 +0200156 global_type_add(name)
157
158 def __repr__(self):
159 return self.name + str(self.flags) + str(self.block)
160
161
Ole Troan53fffa12018-11-13 12:36:56 +0100162class Using():
163 def __init__(self, name, alias):
164 global global_crc
165 self.name = name
166
167 if isinstance(alias, Array):
Paul Vinciguerraf4647ed2019-02-12 12:21:01 -0800168 a = { 'type': alias.fieldtype, # noqa: E201
169 'length': alias.length } # noqa: E202
Ole Troan53fffa12018-11-13 12:36:56 +0100170 else:
Paul Vinciguerraf4647ed2019-02-12 12:21:01 -0800171 a = { 'type': alias.fieldtype } # noqa: E201,E202
Ole Troan53fffa12018-11-13 12:36:56 +0100172 self.alias = a
Paul Vinciguerra7e0c48e2019-02-01 19:37:45 -0800173 self.crc = binascii.crc32(str(alias).encode()) & 0xffffffff
174 global_crc = binascii.crc32(str(alias).encode(), global_crc)
Ole Troan53fffa12018-11-13 12:36:56 +0100175 global_type_add(name)
176
177 def __repr__(self):
178 return self.name + str(self.alias)
179
180
Ole Troan2c2feab2018-04-24 00:02:37 -0400181class Union():
182 def __init__(self, name, block):
183 self.type = 'Union'
184 self.manual_print = False
185 self.manual_endian = False
186 global global_crc
187 self.name = name
188 self.block = block
Ole Troan58914252018-10-23 10:50:07 +0200189 self.crc = crc_block(block)
Ole Troan2c2feab2018-04-24 00:02:37 -0400190 global_type_add(name)
191
192 def __repr__(self):
193 return str(self.block)
194
195
Ole Troan9d420872017-10-12 13:06:35 +0200196class Define():
197 def __init__(self, name, flags, block):
198 self.name = name
199 self.flags = flags
200 self.block = block
Ole Troan58914252018-10-23 10:50:07 +0200201 self.crc = crc_block(block)
Ole Troan9d420872017-10-12 13:06:35 +0200202 self.dont_trace = False
203 self.manual_print = False
204 self.manual_endian = False
205 self.autoreply = False
206 self.singular = False
207 for f in flags:
Ole Troan2c2feab2018-04-24 00:02:37 -0400208 if f == 'dont_trace':
Ole Troan9d420872017-10-12 13:06:35 +0200209 self.dont_trace = True
210 elif f == 'manual_print':
211 self.manual_print = True
212 elif f == 'manual_endian':
213 self.manual_endian = True
214 elif f == 'autoreply':
215 self.autoreply = True
216
217 for b in block:
218 if isinstance(b, Option):
219 if b[1] == 'singular' and b[2] == 'true':
220 self.singular = True
221 block.remove(b)
222
223 def __repr__(self):
224 return self.name + str(self.flags) + str(self.block)
225
226
227class Enum():
228 def __init__(self, name, block, enumtype='u32'):
229 self.name = name
230 self.enumtype = enumtype
Ole Troan2c2feab2018-04-24 00:02:37 -0400231
Ole Troan9d420872017-10-12 13:06:35 +0200232 count = 0
233 for i, b in enumerate(block):
234 if type(b) is list:
235 count = b[1]
236 else:
237 count += 1
238 block[i] = [b, count]
239
240 self.block = block
Ole Troan58914252018-10-23 10:50:07 +0200241 self.crc = crc_block(block)
Ole Troan9d420872017-10-12 13:06:35 +0200242 global_type_add(name)
243
244 def __repr__(self):
245 return self.name + str(self.block)
246
247
248class Import():
249 def __init__(self, filename):
250 self.filename = filename
251
252 # Deal with imports
253 parser = VPPAPI(filename=filename)
254 dirlist = dirlist_get()
255 f = filename
256 for dir in dirlist:
257 f = os.path.join(dir, filename)
258 if os.path.exists(f):
259 break
Ole Troan58914252018-10-23 10:50:07 +0200260 if sys.version[0] == '2':
261 with open(f) as fd:
262 self.result = parser.parse_file(fd, None)
263 else:
264 with open(f, encoding='utf-8') as fd:
265 self.result = parser.parse_file(fd, None)
Ole Troan9d420872017-10-12 13:06:35 +0200266
267 def __repr__(self):
268 return self.filename
269
270
271class Option():
272 def __init__(self, option):
273 self.option = option
Ole Troan58914252018-10-23 10:50:07 +0200274 self.crc = crc_block(option)
Ole Troan9d420872017-10-12 13:06:35 +0200275
276 def __repr__(self):
277 return str(self.option)
278
279 def __getitem__(self, index):
280 return self.option[index]
281
282
283class Array():
284 def __init__(self, fieldtype, name, length):
285 self.type = 'Array'
286 self.fieldtype = fieldtype
287 self.fieldname = name
288 if type(length) is str:
289 self.lengthfield = length
290 self.length = 0
291 else:
292 self.length = length
293 self.lengthfield = None
294
295 def __repr__(self):
296 return str([self.fieldtype, self.fieldname, self.length,
297 self.lengthfield])
298
299
300class Field():
301 def __init__(self, fieldtype, name):
302 self.type = 'Field'
303 self.fieldtype = fieldtype
304 self.fieldname = name
305
306 def __repr__(self):
307 return str([self.fieldtype, self.fieldname])
308
309
310class Coord(object):
311 """ Coordinates of a syntactic element. Consists of:
312 - File name
313 - Line number
314 - (optional) column number, for the Lexer
315 """
316 __slots__ = ('file', 'line', 'column', '__weakref__')
317
318 def __init__(self, file, line, column=None):
319 self.file = file
320 self.line = line
321 self.column = column
322
323 def __str__(self):
324 str = "%s:%s" % (self.file, self.line)
325 if self.column:
326 str += ":%s" % self.column
327 return str
328
329
330class ParseError(Exception):
331 pass
332
333
334#
335# Grammar rules
336#
337class VPPAPIParser(object):
338 tokens = VPPAPILexer.tokens
339
340 def __init__(self, filename, logger):
341 self.filename = filename
342 self.logger = logger
343 self.fields = []
344
345 def _parse_error(self, msg, coord):
346 raise ParseError("%s: %s" % (coord, msg))
347
348 def _parse_warning(self, msg, coord):
349 if self.logger:
350 self.logger.warning("%s: %s" % (coord, msg))
351
352 def _coord(self, lineno, column=None):
353 return Coord(
354 file=self.filename,
355 line=lineno, column=column)
356
357 def _token_coord(self, p, token_idx):
358 """ Returns the coordinates for the YaccProduction object 'p' indexed
359 with 'token_idx'. The coordinate includes the 'lineno' and
360 'column'. Both follow the lex semantic, starting from 1.
361 """
362 last_cr = p.lexer.lexdata.rfind('\n', 0, p.lexpos(token_idx))
363 if last_cr < 0:
364 last_cr = -1
365 column = (p.lexpos(token_idx) - (last_cr))
366 return self._coord(p.lineno(token_idx), column)
367
368 def p_slist(self, p):
369 '''slist : stmt
370 | slist stmt'''
371 if len(p) == 2:
372 p[0] = [p[1]]
373 else:
374 p[0] = p[1] + [p[2]]
375
376 def p_stmt(self, p):
377 '''stmt : define
378 | typedef
379 | option
380 | import
381 | enum
Ole Troan2c2feab2018-04-24 00:02:37 -0400382 | union
Ole Troan9d420872017-10-12 13:06:35 +0200383 | service'''
384 p[0] = p[1]
385
386 def p_import(self, p):
387 '''import : IMPORT STRING_LITERAL ';' '''
388 p[0] = Import(p[2])
389
390 def p_service(self, p):
391 '''service : SERVICE '{' service_statements '}' ';' '''
392 p[0] = p[3]
393
394 def p_service_statements(self, p):
395 '''service_statements : service_statement
396 | service_statements service_statement'''
397 if len(p) == 2:
398 p[0] = [p[1]]
399 else:
400 p[0] = p[1] + [p[2]]
401
402 def p_service_statement(self, p):
Marek Gradzki51e59682018-03-06 10:05:44 +0100403 '''service_statement : RPC ID RETURNS NULL ';'
404 | RPC ID RETURNS ID ';'
Ole Troan9d420872017-10-12 13:06:35 +0200405 | RPC ID RETURNS STREAM ID ';'
406 | RPC ID RETURNS ID EVENTS event_list ';' '''
Marek Gradzkifc70e3a2018-03-06 10:56:26 +0100407 if p[2] == p[4]:
408 # Verify that caller and reply differ
Ole Troan17225df2018-04-11 09:50:03 +0200409 self._parse_error(
410 'Reply ID ({}) should not be equal to Caller ID'.format(p[2]),
411 self._token_coord(p, 1))
Ole Troan9d420872017-10-12 13:06:35 +0200412 if len(p) == 8:
413 p[0] = Service(p[2], p[4], p[6])
414 elif len(p) == 7:
415 p[0] = Service(p[2], p[5], stream=True)
416 else:
417 p[0] = Service(p[2], p[4])
418
419 def p_event_list(self, p):
420 '''event_list : events
421 | event_list events '''
422 if len(p) == 2:
423 p[0] = [p[1]]
424 else:
425 p[0] = p[1] + [p[2]]
426
427 def p_event(self, p):
428 '''events : ID
429 | ID ',' '''
430 p[0] = p[1]
431
432 def p_enum(self, p):
433 '''enum : ENUM ID '{' enum_statements '}' ';' '''
434 p[0] = Enum(p[2], p[4])
435
436 def p_enum_type(self, p):
437 ''' enum : ENUM ID ':' enum_size '{' enum_statements '}' ';' '''
438 if len(p) == 9:
439 p[0] = Enum(p[2], p[6], enumtype=p[4])
440 else:
441 p[0] = Enum(p[2], p[4])
442
443 def p_enum_size(self, p):
444 ''' enum_size : U8
445 | U16
446 | U32 '''
447 p[0] = p[1]
448
449 def p_define(self, p):
450 '''define : DEFINE ID '{' block_statements_opt '}' ';' '''
451 self.fields = []
452 p[0] = Define(p[2], [], p[4])
453
454 def p_define_flist(self, p):
455 '''define : flist DEFINE ID '{' block_statements_opt '}' ';' '''
Ole Troan2c2feab2018-04-24 00:02:37 -0400456 # Legacy typedef
457 if 'typeonly' in p[1]:
458 p[0] = Typedef(p[3], p[1], p[5])
459 else:
460 p[0] = Define(p[3], p[1], p[5])
Ole Troan9d420872017-10-12 13:06:35 +0200461
462 def p_flist(self, p):
463 '''flist : flag
464 | flist flag'''
465 if len(p) == 2:
466 p[0] = [p[1]]
467 else:
468 p[0] = p[1] + [p[2]]
469
470 def p_flag(self, p):
471 '''flag : MANUAL_PRINT
472 | MANUAL_ENDIAN
473 | DONT_TRACE
474 | TYPEONLY
475 | AUTOREPLY'''
476 if len(p) == 1:
477 return
478 p[0] = p[1]
479
480 def p_typedef(self, p):
481 '''typedef : TYPEDEF ID '{' block_statements_opt '}' ';' '''
482 p[0] = Typedef(p[2], [], p[4])
483
Ole Troan53fffa12018-11-13 12:36:56 +0100484 def p_typedef_alias(self, p):
485 '''typedef : TYPEDEF declaration '''
486 p[0] = Using(p[2].fieldname, p[2])
487
Ole Troan9d420872017-10-12 13:06:35 +0200488 def p_block_statements_opt(self, p):
Ole Troan2c2feab2018-04-24 00:02:37 -0400489 '''block_statements_opt : block_statements '''
Ole Troan9d420872017-10-12 13:06:35 +0200490 p[0] = p[1]
491
492 def p_block_statements(self, p):
493 '''block_statements : block_statement
494 | block_statements block_statement'''
495 if len(p) == 2:
496 p[0] = [p[1]]
497 else:
498 p[0] = p[1] + [p[2]]
499
500 def p_block_statement(self, p):
501 '''block_statement : declaration
502 | option '''
503 p[0] = p[1]
504
505 def p_enum_statements(self, p):
506 '''enum_statements : enum_statement
507 | enum_statements enum_statement'''
508 if len(p) == 2:
509 p[0] = [p[1]]
510 else:
511 p[0] = p[1] + [p[2]]
512
513 def p_enum_statement(self, p):
514 '''enum_statement : ID '=' NUM ','
515 | ID ',' '''
516 if len(p) == 5:
517 p[0] = [p[1], p[3]]
518 else:
519 p[0] = p[1]
520
521 def p_declaration(self, p):
522 '''declaration : type_specifier ID ';' '''
523 if len(p) != 4:
524 self._parse_error('ERROR')
525 self.fields.append(p[2])
526 p[0] = Field(p[1], p[2])
527
528 def p_declaration_array(self, p):
529 '''declaration : type_specifier ID '[' NUM ']' ';'
530 | type_specifier ID '[' ID ']' ';' '''
531 if len(p) != 7:
532 return self._parse_error(
533 'array: %s' % p.value,
534 self._coord(lineno=p.lineno))
535
536 # Make this error later
537 if type(p[4]) is int and p[4] == 0:
538 # XXX: Line number is wrong
539 self._parse_warning('Old Style VLA: {} {}[{}];'
540 .format(p[1], p[2], p[4]),
541 self._token_coord(p, 1))
542
543 if type(p[4]) is str and p[4] not in self.fields:
544 # Verify that length field exists
545 self._parse_error('Missing length field: {} {}[{}];'
546 .format(p[1], p[2], p[4]),
547 self._token_coord(p, 1))
548 p[0] = Array(p[1], p[2], p[4])
549
550 def p_option(self, p):
551 '''option : OPTION ID '=' assignee ';' '''
552 p[0] = Option([p[1], p[2], p[4]])
553
554 def p_assignee(self, p):
555 '''assignee : NUM
556 | TRUE
557 | FALSE
558 | STRING_LITERAL '''
559 p[0] = p[1]
560
561 def p_type_specifier(self, p):
562 '''type_specifier : U8
563 | U16
564 | U32
565 | U64
566 | I8
567 | I16
568 | I32
569 | I64
570 | F64
571 | BOOL
572 | STRING'''
573 p[0] = p[1]
574
575 # Do a second pass later to verify that user defined types are defined
576 def p_typedef_specifier(self, p):
577 '''type_specifier : ID '''
578 if p[1] not in global_types:
579 self._parse_error('Undefined type: {}'.format(p[1]),
580 self._token_coord(p, 1))
581 p[0] = p[1]
582
Ole Troan2c2feab2018-04-24 00:02:37 -0400583 def p_union(self, p):
584 '''union : UNION ID '{' block_statements_opt '}' ';' '''
585 p[0] = Union(p[2], p[4])
586
Ole Troan9d420872017-10-12 13:06:35 +0200587 # Error rule for syntax errors
588 def p_error(self, p):
589 if p:
590 self._parse_error(
591 'before: %s' % p.value,
592 self._coord(lineno=p.lineno))
593 else:
594 self._parse_error('At end of input', self.filename)
595
596
597class VPPAPI(object):
598
599 def __init__(self, debug=False, filename='', logger=None):
600 self.lexer = lex.lex(module=VPPAPILexer(filename), debug=debug)
601 self.parser = yacc.yacc(module=VPPAPIParser(filename, logger),
Ole Troand6743b12018-03-07 08:40:58 +0100602 write_tables=False, debug=debug)
Ole Troan9d420872017-10-12 13:06:35 +0200603 self.logger = logger
604
605 def parse_string(self, code, debug=0, lineno=1):
606 self.lexer.lineno = lineno
607 return self.parser.parse(code, lexer=self.lexer, debug=debug)
608
609 def parse_file(self, fd, debug=0):
610 data = fd.read()
611 return self.parse_string(data, debug=debug)
612
613 def autoreply_block(self, name):
614 block = [Field('u32', 'context'),
615 Field('i32', 'retval')]
616 return Define(name + '_reply', [], block)
617
618 def process(self, objs):
619 s = {}
Ole Troan2c2feab2018-04-24 00:02:37 -0400620 s['Option'] = {}
621 s['Define'] = []
622 s['Service'] = []
623 s['types'] = []
624 s['Import'] = []
Ole Troan53fffa12018-11-13 12:36:56 +0100625 s['Alias'] = {}
Ole Troan9d420872017-10-12 13:06:35 +0200626 for o in objs:
Ole Troan2c2feab2018-04-24 00:02:37 -0400627 tname = o.__class__.__name__
Ole Troan9d420872017-10-12 13:06:35 +0200628 if isinstance(o, Define):
Ole Troan2c2feab2018-04-24 00:02:37 -0400629 s[tname].append(o)
630 if o.autoreply:
631 s[tname].append(self.autoreply_block(o.name))
Ole Troan9d420872017-10-12 13:06:35 +0200632 elif isinstance(o, Option):
Ole Troan2c2feab2018-04-24 00:02:37 -0400633 s[tname][o[1]] = o[2]
Ole Troan9d420872017-10-12 13:06:35 +0200634 elif type(o) is list:
635 for o2 in o:
636 if isinstance(o2, Service):
Ole Troan2c2feab2018-04-24 00:02:37 -0400637 s['Service'].append(o2)
Ole Troan58914252018-10-23 10:50:07 +0200638 elif (isinstance(o, Enum) or
639 isinstance(o, Typedef) or
640 isinstance(o, Union)):
Ole Troan2c2feab2018-04-24 00:02:37 -0400641 s['types'].append(o)
Ole Troan53fffa12018-11-13 12:36:56 +0100642 elif isinstance(o, Using):
643 s['Alias'][o.name] = o.alias
Ole Troan2c2feab2018-04-24 00:02:37 -0400644 else:
645 if tname not in s:
Ole Troan58914252018-10-23 10:50:07 +0200646 raise ValueError('Unknown class type: {} {}'
647 .format(tname, o))
Ole Troan2c2feab2018-04-24 00:02:37 -0400648 s[tname].append(o)
Ole Troan9d420872017-10-12 13:06:35 +0200649
Ole Troan2c2feab2018-04-24 00:02:37 -0400650 msgs = {d.name: d for d in s['Define']}
651 svcs = {s.caller: s for s in s['Service']}
652 replies = {s.reply: s for s in s['Service']}
Marek Gradzki51e59682018-03-06 10:05:44 +0100653 seen_services = {}
Ole Troan9d420872017-10-12 13:06:35 +0200654
655 for service in svcs:
656 if service not in msgs:
Ole Troan17225df2018-04-11 09:50:03 +0200657 raise ValueError(
658 'Service definition refers to unknown message'
659 ' definition: {}'.format(service))
660 if svcs[service].reply != 'null' and \
661 svcs[service].reply not in msgs:
Ole Troan9d420872017-10-12 13:06:35 +0200662 raise ValueError('Service definition refers to unknown message'
663 ' definition in reply: {}'
664 .format(svcs[service].reply))
Marek Gradzkib533f3f2018-03-06 11:10:56 +0100665 if service in replies:
666 raise ValueError('Service definition refers to message'
667 ' marked as reply: {}'.format(service))
Ole Troan9d420872017-10-12 13:06:35 +0200668 for event in svcs[service].events:
669 if event not in msgs:
670 raise ValueError('Service definition refers to unknown '
671 'event: {} in message: {}'
672 .format(event, service))
Marek Gradzki51e59682018-03-06 10:05:44 +0100673 seen_services[event] = True
Ole Troan9d420872017-10-12 13:06:35 +0200674
Marek Gradzki51e59682018-03-06 10:05:44 +0100675 # Create services implicitly
Ole Troan9d420872017-10-12 13:06:35 +0200676 for d in msgs:
Marek Gradzki51e59682018-03-06 10:05:44 +0100677 if d in seen_services:
678 continue
Ole Troan9d420872017-10-12 13:06:35 +0200679 if msgs[d].singular is True:
680 continue
Ole Troan9d420872017-10-12 13:06:35 +0200681 if d.endswith('_reply'):
682 if d[:-6] in svcs:
683 continue
684 if d[:-6] not in msgs:
Marek Gradzkicc134712018-03-06 12:25:02 +0100685 raise ValueError('{} missing calling message'
686 .format(d))
Ole Troan9d420872017-10-12 13:06:35 +0200687 continue
688 if d.endswith('_dump'):
689 if d in svcs:
690 continue
691 if d[:-5]+'_details' in msgs:
Ole Troan2c2feab2018-04-24 00:02:37 -0400692 s['Service'].append(Service(d, d[:-5]+'_details',
Ole Troan58914252018-10-23 10:50:07 +0200693 stream=True))
Ole Troan9d420872017-10-12 13:06:35 +0200694 else:
Marek Gradzkicc134712018-03-06 12:25:02 +0100695 raise ValueError('{} missing details message'
696 .format(d))
Ole Troan9d420872017-10-12 13:06:35 +0200697 continue
698
699 if d.endswith('_details'):
700 if d[:-8]+'_dump' not in msgs:
Marek Gradzkicc134712018-03-06 12:25:02 +0100701 raise ValueError('{} missing dump message'
702 .format(d))
Ole Troan9d420872017-10-12 13:06:35 +0200703 continue
704
705 if d in svcs:
706 continue
707 if d+'_reply' in msgs:
Ole Troan2c2feab2018-04-24 00:02:37 -0400708 s['Service'].append(Service(d, d+'_reply'))
Ole Troan9d420872017-10-12 13:06:35 +0200709 else:
Ole Troan17225df2018-04-11 09:50:03 +0200710 raise ValueError(
711 '{} missing reply message ({}) or service definition'
712 .format(d, d+'_reply'))
Ole Troan9d420872017-10-12 13:06:35 +0200713
714 return s
715
Ole Troan2c2feab2018-04-24 00:02:37 -0400716 def process_imports(self, objs, in_import, result):
Marek Gradzki51e59682018-03-06 10:05:44 +0100717 imported_objs = []
Ole Troan9d420872017-10-12 13:06:35 +0200718 for o in objs:
Ole Troan2c2feab2018-04-24 00:02:37 -0400719 # Only allow the following object types from imported file
720 if in_import and not (isinstance(o, Enum) or
721 isinstance(o, Union) or
Ole Troan10a09892018-06-29 11:32:33 +0200722 isinstance(o, Typedef) or
Ole Troan53fffa12018-11-13 12:36:56 +0100723 isinstance(o, Import) or
724 isinstance(o, Using)):
Ole Troan2c2feab2018-04-24 00:02:37 -0400725 continue
Ole Troan2c2feab2018-04-24 00:02:37 -0400726 if isinstance(o, Import):
727 self.process_imports(o.result, True, result)
Ole Troan10a09892018-06-29 11:32:33 +0200728 else:
729 result.append(o)
Ole Troan9d420872017-10-12 13:06:35 +0200730
Ole Troan58914252018-10-23 10:50:07 +0200731
Ole Troan9d420872017-10-12 13:06:35 +0200732# Add message ids to each message.
733def add_msg_id(s):
734 for o in s:
735 o.block.insert(0, Field('u16', '_vl_msg_id'))
736 return s
737
738
Ole Troan9d420872017-10-12 13:06:35 +0200739dirlist = []
740
741
742def dirlist_add(dirs):
743 global dirlist
744 if dirs:
745 dirlist = dirlist + dirs
746
747
748def dirlist_get():
749 return dirlist
750
751
752#
753# Main
754#
755def main():
Ole Troan9d420872017-10-12 13:06:35 +0200756 cliparser = argparse.ArgumentParser(description='VPP API generator')
757 cliparser.add_argument('--pluginpath', default=""),
758 cliparser.add_argument('--includedir', action='append'),
Ole Troan58914252018-10-23 10:50:07 +0200759 if sys.version[0] == '2':
760 cliparser.add_argument('--input', type=argparse.FileType('r'),
761 default=sys.stdin)
Paul Vinciguerra7e0c48e2019-02-01 19:37:45 -0800762 cliparser.add_argument('--output', nargs='?',
763 type=argparse.FileType('w'),
764 default=sys.stdout)
765
Ole Troan58914252018-10-23 10:50:07 +0200766 else:
767 cliparser.add_argument('--input',
768 type=argparse.FileType('r', encoding='UTF-8'),
769 default=sys.stdin)
Paul Vinciguerra7e0c48e2019-02-01 19:37:45 -0800770 cliparser.add_argument('--output', nargs='?',
771 type=argparse.FileType('w', encoding='UTF-8'),
772 default=sys.stdout)
Ole Troan9d420872017-10-12 13:06:35 +0200773
774 cliparser.add_argument('output_module', nargs='?', default='C')
775 cliparser.add_argument('--debug', action='store_true')
776 cliparser.add_argument('--show-name', nargs=1)
777 args = cliparser.parse_args()
778
779 dirlist_add(args.includedir)
780 if not args.debug:
781 sys.excepthook = exception_handler
782
783 # Filename
784 if args.show_name:
785 filename = args.show_name[0]
786 elif args.input != sys.stdin:
787 filename = args.input.name
788 else:
789 filename = ''
790
Marek Gradzki51e59682018-03-06 10:05:44 +0100791 if args.debug:
792 logging.basicConfig(stream=sys.stdout, level=logging.WARNING)
793 else:
794 logging.basicConfig()
795 log = logging.getLogger('vppapigen')
796
Ole Troan9d420872017-10-12 13:06:35 +0200797 parser = VPPAPI(debug=args.debug, filename=filename, logger=log)
Ole Troan2c2feab2018-04-24 00:02:37 -0400798 parsed_objects = parser.parse_file(args.input, log)
Ole Troan9d420872017-10-12 13:06:35 +0200799
800 # Build a list of objects. Hash of lists.
Ole Troan2c2feab2018-04-24 00:02:37 -0400801 result = []
802 parser.process_imports(parsed_objects, False, result)
Ole Troan9d420872017-10-12 13:06:35 +0200803 s = parser.process(result)
804
805 # Add msg_id field
Ole Troan2c2feab2018-04-24 00:02:37 -0400806 s['Define'] = add_msg_id(s['Define'])
Ole Troan9d420872017-10-12 13:06:35 +0200807
Ole Troan17225df2018-04-11 09:50:03 +0200808 file_crc = global_crc & 0xffffffff
Ole Troan9d420872017-10-12 13:06:35 +0200809
810 #
811 # Debug
812 if args.debug:
813 import pprint
Ole Troan10a09892018-06-29 11:32:33 +0200814 pp = pprint.PrettyPrinter(indent=4, stream=sys.stderr)
Ole Troan2c2feab2018-04-24 00:02:37 -0400815 for t in s['Define']:
Ole Troan9d420872017-10-12 13:06:35 +0200816 pp.pprint([t.name, t.flags, t.block])
Ole Troan2c2feab2018-04-24 00:02:37 -0400817 for t in s['types']:
818 pp.pprint([t.name, t.block])
Ole Troan9d420872017-10-12 13:06:35 +0200819
820 #
821 # Generate representation
822 #
Paul Vinciguerraf4647ed2019-02-12 12:21:01 -0800823 from importlib.machinery import SourceFileLoader
Ole Troan9d420872017-10-12 13:06:35 +0200824
825 # Default path
Ole Troan30787372018-03-01 13:33:39 +0100826 pluginpath = ''
Ole Troan9d420872017-10-12 13:06:35 +0200827 if not args.pluginpath:
Ole Troan30787372018-03-01 13:33:39 +0100828 cand = []
829 cand.append(os.path.dirname(os.path.realpath(__file__)))
Ole Troan17225df2018-04-11 09:50:03 +0200830 cand.append(os.path.dirname(os.path.realpath(__file__)) +
Ole Troan30787372018-03-01 13:33:39 +0100831 '/../share/vpp/')
832 for c in cand:
833 c += '/'
Ole Troan58914252018-10-23 10:50:07 +0200834 if os.path.isfile('{}vppapigen_{}.py'
835 .format(c, args.output_module.lower())):
Ole Troan30787372018-03-01 13:33:39 +0100836 pluginpath = c
837 break
Ole Troan9d420872017-10-12 13:06:35 +0200838 else:
839 pluginpath = args.pluginpath + '/'
Ole Troan30787372018-03-01 13:33:39 +0100840 if pluginpath == '':
841 raise Exception('Output plugin not found')
Ole Troan58914252018-10-23 10:50:07 +0200842 module_path = '{}vppapigen_{}.py'.format(pluginpath,
843 args.output_module.lower())
Ole Troan9d420872017-10-12 13:06:35 +0200844
845 try:
Paul Vinciguerraf4647ed2019-02-12 12:21:01 -0800846 plugin = SourceFileLoader(args.output_module,
847 module_path).load_module()
Ole Troan58914252018-10-23 10:50:07 +0200848 except Exception as err:
Ole Troan9d420872017-10-12 13:06:35 +0200849 raise Exception('Error importing output plugin: {}, {}'
850 .format(module_path, err))
851
852 result = plugin.run(filename, s, file_crc)
853 if result:
Ole Troan17225df2018-04-11 09:50:03 +0200854 print(result, file=args.output)
Ole Troan9d420872017-10-12 13:06:35 +0200855 else:
856 raise Exception('Running plugin failed: {} {}'
857 .format(filename, result))
858
859
860if __name__ == '__main__':
861 main()