X-Git-Url: https://gerrit.fd.io/r/gitweb?p=vpp.git;a=blobdiff_plain;f=src%2Ftools%2Fvppapigen%2Fvppapigen.py;h=4ca9b954e671bed432f2b410151c554b514013a5;hp=e6237a753f977811edd28e1c0fa83bfd9f9160f1;hb=58914254f0abb0f6c69d866e84915d469d128bd8;hpb=07dce1e11de9872b5c9695e8cbbf89a2bcb3f63d diff --git a/src/tools/vppapigen/vppapigen.py b/src/tools/vppapigen/vppapigen.py index e6237a753f9..4ca9b954e67 100755 --- a/src/tools/vppapigen/vppapigen.py +++ b/src/tools/vppapigen/vppapigen.py @@ -9,12 +9,16 @@ import logging import binascii import os +# Ensure we don't leave temporary files around +sys.dont_write_bytecode = True + # # VPP API language # # Global dictionary of new types (including enums) global_types = {} +global_crc = 0 def global_type_add(name): @@ -27,7 +31,7 @@ def global_type_add(name): # All your trace are belong to us! def exception_handler(exception_type, exception, traceback): - print ("%s: %s" % (exception_type.__name__, exception)) + print("%s: %s" % (exception_type.__name__, exception)) # @@ -67,6 +71,7 @@ class VPPAPILexer(object): 'import': 'IMPORT', 'true': 'TRUE', 'false': 'FALSE', + 'union': 'UNION', } tokens = ['STRING_LITERAL', @@ -116,6 +121,17 @@ class VPPAPILexer(object): # A string containing ignored characters (spaces and tabs) t_ignore = ' \t' + +# +# Side-effect: Sets global_crc +# +def crc_block(block): + global global_crc + s = str(block).encode() + global_crc = binascii.crc32(s, global_crc) + return binascii.crc32(s) & 0xffffffff + + class Service(): def __init__(self, caller, reply, events=[], stream=False): self.caller = caller @@ -129,30 +145,67 @@ class Typedef(): self.name = name self.flags = flags self.block = block - self.crc = binascii.crc32(str(block)) & 0xffffffff + self.crc = crc_block(block) + self.manual_print = False + self.manual_endian = False + for f in flags: + if f == 'manual_print': + self.manual_print = True + elif f == 'manual_endian': + self.manual_endian = True global_type_add(name) def __repr__(self): return self.name + str(self.flags) + str(self.block) +class Using(): + def __init__(self, name, alias): + global global_crc + self.name = name + + if isinstance(alias, Array): + a = { 'type': alias.fieldtype, + 'length': alias.length } + else: + a = { 'type': alias.fieldtype } + self.alias = a + self.crc = binascii.crc32(str(alias)) & 0xffffffff + global_crc = binascii.crc32(str(alias), global_crc) + global_type_add(name) + + def __repr__(self): + return self.name + str(self.alias) + + +class Union(): + def __init__(self, name, block): + self.type = 'Union' + self.manual_print = False + self.manual_endian = False + global global_crc + self.name = name + self.block = block + self.crc = crc_block(block) + global_type_add(name) + + def __repr__(self): + return str(self.block) + + class Define(): def __init__(self, name, flags, block): self.name = name self.flags = flags self.block = block - self.crc = binascii.crc32(str(block)) & 0xffffffff - self.typeonly = False + self.crc = crc_block(block) self.dont_trace = False self.manual_print = False self.manual_endian = False self.autoreply = False self.singular = False for f in flags: - if f == 'typeonly': - self.typeonly = True - global_type_add(name) - elif f == 'dont_trace': + if f == 'dont_trace': self.dont_trace = True elif f == 'manual_print': self.manual_print = True @@ -175,6 +228,7 @@ class Enum(): def __init__(self, name, block, enumtype='u32'): self.name = name self.enumtype = enumtype + count = 0 for i, b in enumerate(block): if type(b) is list: @@ -184,7 +238,7 @@ class Enum(): block[i] = [b, count] self.block = block - self.crc = binascii.crc32(str(block)) & 0xffffffff + self.crc = crc_block(block) global_type_add(name) def __repr__(self): @@ -203,8 +257,12 @@ class Import(): f = os.path.join(dir, filename) if os.path.exists(f): break - with open(f) as fd: - self.result = parser.parse_file(fd, None) + if sys.version[0] == '2': + with open(f) as fd: + self.result = parser.parse_file(fd, None) + else: + with open(f, encoding='utf-8') as fd: + self.result = parser.parse_file(fd, None) def __repr__(self): return self.filename @@ -213,7 +271,7 @@ class Import(): class Option(): def __init__(self, option): self.option = option - self.crc = binascii.crc32(str(option)) & 0xffffffff + self.crc = crc_block(option) def __repr__(self): return str(self.option) @@ -321,6 +379,7 @@ class VPPAPIParser(object): | option | import | enum + | union | service''' p[0] = p[1] @@ -347,8 +406,9 @@ class VPPAPIParser(object): | RPC ID RETURNS ID EVENTS event_list ';' ''' if p[2] == p[4]: # Verify that caller and reply differ - self._parse_error('Reply ID ({}) should not be equal to Caller ID'.format(p[2]), - self._token_coord(p, 1)) + self._parse_error( + 'Reply ID ({}) should not be equal to Caller ID'.format(p[2]), + self._token_coord(p, 1)) if len(p) == 8: p[0] = Service(p[2], p[4], p[6]) elif len(p) == 7: @@ -393,7 +453,11 @@ class VPPAPIParser(object): def p_define_flist(self, p): '''define : flist DEFINE ID '{' block_statements_opt '}' ';' ''' - p[0] = Define(p[3], p[1], p[5]) + # Legacy typedef + if 'typeonly' in p[1]: + p[0] = Typedef(p[3], p[1], p[5]) + else: + p[0] = Define(p[3], p[1], p[5]) def p_flist(self, p): '''flist : flag @@ -417,8 +481,12 @@ class VPPAPIParser(object): '''typedef : TYPEDEF ID '{' block_statements_opt '}' ';' ''' p[0] = Typedef(p[2], [], p[4]) + def p_typedef_alias(self, p): + '''typedef : TYPEDEF declaration ''' + p[0] = Using(p[2].fieldname, p[2]) + def p_block_statements_opt(self, p): - '''block_statements_opt : block_statements''' + '''block_statements_opt : block_statements ''' p[0] = p[1] def p_block_statements(self, p): @@ -512,6 +580,10 @@ class VPPAPIParser(object): self._token_coord(p, 1)) p[0] = p[1] + def p_union(self, p): + '''union : UNION ID '{' block_statements_opt '}' ';' ''' + p[0] = Union(p[2], p[4]) + # Error rule for syntax errors def p_error(self, p): if p: @@ -527,7 +599,7 @@ class VPPAPI(object): def __init__(self, debug=False, filename='', logger=None): self.lexer = lex.lex(module=VPPAPILexer(filename), debug=debug) self.parser = yacc.yacc(module=VPPAPIParser(filename, logger), - tabmodule='vppapigentab', debug=debug) + write_tables=False, debug=debug) self.logger = logger def parse_string(self, code, debug=0, lineno=1): @@ -545,43 +617,48 @@ class VPPAPI(object): def process(self, objs): s = {} - s['defines'] = [] - s['typedefs'] = [] - s['imports'] = [] - s['options'] = {} - s['enums'] = [] - s['services'] = [] - + s['Option'] = {} + s['Define'] = [] + s['Service'] = [] + s['types'] = [] + s['Import'] = [] + s['Alias'] = {} for o in objs: + tname = o.__class__.__name__ if isinstance(o, Define): - if o.typeonly: - s['typedefs'].append(o) - else: - s['defines'].append(o) - if o.autoreply: - s['defines'].append(self.autoreply_block(o.name)) + s[tname].append(o) + if o.autoreply: + s[tname].append(self.autoreply_block(o.name)) elif isinstance(o, Option): - s['options'][o[1]] = o[2] - elif isinstance(o, Enum): - s['enums'].append(o) - elif isinstance(o, Typedef): - s['typedefs'].append(o) + s[tname][o[1]] = o[2] elif type(o) is list: for o2 in o: if isinstance(o2, Service): - s['services'].append(o2) - - - msgs = {d.name: d for d in s['defines']} - svcs = {s.caller: s for s in s['services']} - replies = {s.reply: s for s in s['services']} + s['Service'].append(o2) + elif (isinstance(o, Enum) or + isinstance(o, Typedef) or + isinstance(o, Union)): + s['types'].append(o) + elif isinstance(o, Using): + s['Alias'][o.name] = o.alias + else: + if tname not in s: + raise ValueError('Unknown class type: {} {}' + .format(tname, o)) + s[tname].append(o) + + msgs = {d.name: d for d in s['Define']} + svcs = {s.caller: s for s in s['Service']} + replies = {s.reply: s for s in s['Service']} seen_services = {} for service in svcs: if service not in msgs: - raise ValueError('Service definition refers to unknown message' - ' definition: {}'.format(service)) - if svcs[service].reply != 'null' and svcs[service].reply not in msgs: + raise ValueError( + 'Service definition refers to unknown message' + ' definition: {}'.format(service)) + if svcs[service].reply != 'null' and \ + svcs[service].reply not in msgs: raise ValueError('Service definition refers to unknown message' ' definition in reply: {}' .format(svcs[service].reply)) @@ -605,47 +682,51 @@ class VPPAPI(object): if d[:-6] in svcs: continue if d[:-6] not in msgs: - self.logger.warning('{} missing calling message' - .format(d)) + raise ValueError('{} missing calling message' + .format(d)) continue if d.endswith('_dump'): if d in svcs: continue if d[:-5]+'_details' in msgs: - s['services'].append(Service(d, d[:-5]+'_details', - stream=True)) + s['Service'].append(Service(d, d[:-5]+'_details', + stream=True)) else: - self.logger.error('{} missing details message' - .format(d)) + raise ValueError('{} missing details message' + .format(d)) continue if d.endswith('_details'): if d[:-8]+'_dump' not in msgs: - self.logger.error('{} missing dump message' - .format(d)) + raise ValueError('{} missing dump message' + .format(d)) continue if d in svcs: continue if d+'_reply' in msgs: - s['services'].append(Service(d, d+'_reply')) + s['Service'].append(Service(d, d+'_reply')) else: - raise ValueError('{} missing reply message ({}) or service definition' - .format(d, d+'_reply')) + raise ValueError( + '{} missing reply message ({}) or service definition' + .format(d, d+'_reply')) return s - def process_imports(self, objs, in_import): + def process_imports(self, objs, in_import, result): imported_objs = [] for o in objs: + # Only allow the following object types from imported file + if in_import and not (isinstance(o, Enum) or + isinstance(o, Union) or + isinstance(o, Typedef) or + isinstance(o, Import) or + isinstance(o, Using)): + continue if isinstance(o, Import): - return objs + self.process_imports(o.result, True) - if in_import: - if isinstance(o, Define) and o.typeonly: - imported_objs.append(o) - if in_import: - return imported_objs - return objs + self.process_imports(o.result, True, result) + else: + result.append(o) # Add message ids to each message. @@ -655,10 +736,6 @@ def add_msg_id(s): return s -def getcrc(s): - return binascii.crc32(str(s)) & 0xffffffff - - dirlist = [] @@ -679,8 +756,13 @@ def main(): cliparser = argparse.ArgumentParser(description='VPP API generator') cliparser.add_argument('--pluginpath', default=""), cliparser.add_argument('--includedir', action='append'), - cliparser.add_argument('--input', type=argparse.FileType('r'), - default=sys.stdin) + if sys.version[0] == '2': + cliparser.add_argument('--input', type=argparse.FileType('r'), + default=sys.stdin) + else: + cliparser.add_argument('--input', + type=argparse.FileType('r', encoding='UTF-8'), + default=sys.stdin) cliparser.add_argument('--output', nargs='?', type=argparse.FileType('w'), default=sys.stdout) @@ -707,28 +789,28 @@ def main(): logging.basicConfig() log = logging.getLogger('vppapigen') - parser = VPPAPI(debug=args.debug, filename=filename, logger=log) - result = parser.parse_file(args.input, log) + parsed_objects = parser.parse_file(args.input, log) # Build a list of objects. Hash of lists. - result = parser.process_imports(result, False) + result = [] + parser.process_imports(parsed_objects, False, result) s = parser.process(result) # Add msg_id field - s['defines'] = add_msg_id(s['defines']) + s['Define'] = add_msg_id(s['Define']) - file_crc = getcrc(s) + file_crc = global_crc & 0xffffffff # # Debug if args.debug: import pprint - pp = pprint.PrettyPrinter(indent=4) - for t in s['defines']: - pp.pprint([t.name, t.flags, t.block]) - for t in s['typedefs']: + pp = pprint.PrettyPrinter(indent=4, stream=sys.stderr) + for t in s['Define']: pp.pprint([t.name, t.flags, t.block]) + for t in s['types']: + pp.pprint([t.name, t.block]) # # Generate representation @@ -740,28 +822,30 @@ def main(): if not args.pluginpath: cand = [] cand.append(os.path.dirname(os.path.realpath(__file__))) - cand.append(os.path.dirname(os.path.realpath(__file__)) + \ + cand.append(os.path.dirname(os.path.realpath(__file__)) + '/../share/vpp/') for c in cand: c += '/' - if os.path.isfile(c + args.output_module + '.py'): + if os.path.isfile('{}vppapigen_{}.py' + .format(c, args.output_module.lower())): pluginpath = c break else: pluginpath = args.pluginpath + '/' if pluginpath == '': raise Exception('Output plugin not found') - module_path = pluginpath + args.output_module + '.py' + module_path = '{}vppapigen_{}.py'.format(pluginpath, + args.output_module.lower()) try: plugin = imp.load_source(args.output_module, module_path) - except Exception, err: + except Exception as err: raise Exception('Error importing output plugin: {}, {}' .format(module_path, err)) result = plugin.run(filename, s, file_crc) if result: - print (result, file=args.output) + print(result, file=args.output) else: raise Exception('Running plugin failed: {} {}' .format(filename, result))