vppapigen: implement reversible repr's
[vpp.git] / src / tools / vppapigen / vppapigen.py
index ae2b0b1..94696ae 100755 (executable)
@@ -18,15 +18,12 @@ sys.dont_write_bytecode = True
 
 # Global dictionary of new types (including enums)
 global_types = {}
-global_crc = 0
 
 
-def global_type_add(name):
+def global_type_add(name, obj):
     '''Add new type to the dictionary of types '''
     type_name = 'vl_api_' + name + '_t'
-    if type_name in global_types:
-        raise KeyError('Type is already defined: {}'.format(name))
-    global_types[type_name] = True
+    global_types[type_name] = obj
 
 
 # All your trace are belong to us!
@@ -80,9 +77,12 @@ class VPPAPILexer(object):
     t_ignore_LINE_COMMENT = '//.*'
 
     def t_NUM(self, t):
-        r'0[xX][0-9a-fA-F]+|\d+'
+        r'0[xX][0-9a-fA-F]+|-?\d+\.?\d*'
         base = 16 if t.value.startswith('0x') else 10
-        t.value = int(t.value, base)
+        if '.' in t.value:
+            t.value = float(t.value)
+        else:
+            t.value = int(t.value, base)
         return t
 
     def t_ID(self, t):
@@ -122,14 +122,9 @@ class VPPAPILexer(object):
     t_ignore = ' \t'
 
 
-#
-# Side-effect: Sets global_crc
-#
-def crc_block(block):
-    global global_crc
+def crc_block_combine(block, crc):
     s = str(block).encode()
-    global_crc = binascii.crc32(s, global_crc)
-    return binascii.crc32(s) & 0xffffffff
+    return binascii.crc32(s, crc) & 0xffffffff
 
 
 class Service():
@@ -139,13 +134,17 @@ class Service():
         self.stream = stream
         self.events = [] if events is None else events
 
+    def __repr__(self):
+        return "Service(caller={!r}, reply={!r}, events={!r}, stream={!r})".\
+            format(self.caller, self.reply, self.events, self.stream)
+
 
 class Typedef():
     def __init__(self, name, flags, block):
         self.name = name
         self.flags = flags
         self.block = block
-        self.crc = crc_block(block)
+        self.crc = str(block).encode()
         self.manual_print = False
         self.manual_endian = False
         for f in flags:
@@ -153,29 +152,31 @@ class Typedef():
                 self.manual_print = True
             elif f == 'manual_endian':
                 self.manual_endian = True
-        global_type_add(name)
+        global_type_add(name, self)
 
     def __repr__(self):
-        return self.name + str(self.flags) + str(self.block)
+        return "Typedef(name={!r}, flags={!r}, block={!r})".format(
+            self.name, self.flags, self.block)
 
 
 class Using():
     def __init__(self, name, alias):
-        global global_crc
         self.name = name
 
+        # save constructor values for repr()
+        self._alias = alias
+
         if isinstance(alias, Array):
-            a = { 'type': alias.fieldtype,  # noqa: E201
-                  'length': alias.length }  # noqa: E202
+            a = {'type': alias.fieldtype,
+                 'length': alias.length}
         else:
-            a = { 'type': alias.fieldtype }  # noqa: E201,E202
+            a = {'type': alias.fieldtype}
         self.alias = a
-        self.crc = binascii.crc32(str(alias).encode()) & 0xffffffff
-        global_crc = binascii.crc32(str(alias).encode(), global_crc)
-        global_type_add(name)
+        self.crc = str(alias).encode()
+        global_type_add(name, self)
 
     def __repr__(self):
-        return self.name + str(self.alias)
+        return "Using(name={!r}, alias={!r})".format(self.name, self._alias)
 
 
 class Union():
@@ -183,14 +184,13 @@ class Union():
         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)
+        self.crc = str(block).encode()
+        global_type_add(name, self)
 
     def __repr__(self):
-        return str(self.block)
+        return "Union(name={!r}, block={!r})".format(self.name, self.block)
 
 
 class Define():
@@ -198,7 +198,7 @@ class Define():
         self.name = name
         self.flags = flags
         self.block = block
-        self.crc = crc_block(block)
+        self.crc = str(block).encode()
         self.dont_trace = False
         self.manual_print = False
         self.manual_endian = False
@@ -221,7 +221,8 @@ class Define():
                 block.remove(b)
 
     def __repr__(self):
-        return self.name + str(self.flags) + str(self.block)
+        return "Define(name={!r}, flags={!r}, block={!r})".format(
+            self.name, self.flags, self.block)
 
 
 class Enum():
@@ -238,11 +239,12 @@ class Enum():
                 block[i] = [b, count]
 
         self.block = block
-        self.crc = crc_block(block)
-        global_type_add(name)
+        self.crc = str(block).encode()
+        global_type_add(name, self)
 
     def __repr__(self):
-        return self.name + str(self.block)
+        return "Enum(name={!r}, block={!r}, enumtype={!r})".format(
+            self.name, self.block, self.enumtype)
 
 
 class Import():
@@ -265,16 +267,16 @@ class Import():
                 self.result = parser.parse_file(fd, None)
 
     def __repr__(self):
-        return self.filename
+        return "Import(filename={!r})".format(self.filename)
 
 
 class Option():
     def __init__(self, option):
         self.option = option
-        self.crc = crc_block(option)
+        self.crc = str(option).encode()
 
     def __repr__(self):
-        return str(self.option)
+        return "Option({!r})".format(self.option)
 
     def __getitem__(self, index):
         return self.option[index]
@@ -284,6 +286,11 @@ class Array():
     def __init__(self, fieldtype, name, length):
         self.type = 'Array'
         self.fieldtype = fieldtype
+
+        # save constructor values for repr()
+        self._name = name
+        self._length = length
+
         self.fieldname = name
         if type(length) is str:
             self.lengthfield = length
@@ -293,8 +300,8 @@ class Array():
             self.lengthfield = None
 
     def __repr__(self):
-        return str([self.fieldtype, self.fieldname, self.length,
-                    self.lengthfield])
+        return "Array(fieldtype={!r}, name={!r}, length={!r})".format(
+            self.fieldtype, self._name, self._length)
 
 
 class Field():
@@ -305,7 +312,8 @@ class Field():
         self.limit = limit
 
     def __repr__(self):
-        return str([self.fieldtype, self.fieldname])
+        return "Field(fieldtype={!r}, name={!r}, limit={!r})".format(
+            self.fieldtype, self.fieldname, self.limit)
 
 
 class Coord(object):
@@ -519,18 +527,31 @@ class VPPAPIParser(object):
         else:
             p[0] = p[1]
 
+    def p_field_options(self, p):
+        '''field_options : field_option
+                           | field_options field_option'''
+        if len(p) == 2:
+            p[0] = p[1]
+        else:
+            p[0] = {**p[1], **p[2]}
+
+    def p_field_option(self, p):
+        '''field_option : ID '=' assignee ','
+                        | ID '=' assignee
+        '''
+        p[0] = {p[1]: p[3]}
+
     def p_declaration(self, p):
         '''declaration : type_specifier ID ';'
-                       | type_specifier ID '[' ID '=' assignee ']' ';' '''
-        if len(p) == 9:
-            p[0] = Field(p[1], p[2], {p[4]: p[6]})
+                       | type_specifier ID '[' field_options ']' ';' '''
+        if len(p) == 7:
+            p[0] = Field(p[1], p[2], p[4])
         elif len(p) == 4:
             p[0] = Field(p[1], p[2])
         else:
             self._parse_error('ERROR')
         self.fields.append(p[2])
 
-
     def p_declaration_array(self, p):
         '''declaration : type_specifier ID '[' NUM ']' ';'
                        | type_specifier ID '[' ID ']' ';' '''
@@ -629,8 +650,13 @@ class VPPAPI(object):
         s['types'] = []
         s['Import'] = []
         s['Alias'] = {}
+        crc = 0
         for o in objs:
             tname = o.__class__.__name__
+            try:
+                crc = binascii.crc32(o.crc, crc)
+            except AttributeError:
+                pass
             if isinstance(o, Define):
                 s[tname].append(o)
                 if o.autoreply:
@@ -658,6 +684,8 @@ class VPPAPI(object):
         replies = {s.reply: s for s in s['Service']}
         seen_services = {}
 
+        s['file_crc'] = crc
+
         for service in svcs:
             if service not in msgs:
                 raise ValueError(
@@ -755,6 +783,26 @@ def dirlist_get():
     return dirlist
 
 
+def foldup_blocks(block, crc):
+    for b in block:
+        # Look up CRC in user defined types
+        if b.fieldtype.startswith('vl_api_'):
+            # Recursively
+            t = global_types[b.fieldtype]
+            try:
+                crc = crc_block_combine(t.block, crc)
+                return foldup_blocks(t.block, crc)
+            except:
+                pass
+    return crc
+
+
+def foldup_crcs(s):
+    for f in s:
+        f.crc = foldup_blocks(f.block,
+                              binascii.crc32(f.crc))
+
+
 #
 # Main
 #
@@ -811,7 +859,8 @@ def main():
     # Add msg_id field
     s['Define'] = add_msg_id(s['Define'])
 
-    file_crc = global_crc & 0xffffffff
+    # Fold up CRCs
+    foldup_crcs(s['Define'])
 
     #
     # Debug
@@ -855,7 +904,7 @@ def main():
         raise Exception('Error importing output plugin: {}, {}'
                         .format(module_path, err))
 
-    result = plugin.run(filename, s, file_crc)
+    result = plugin.run(filename, s)
     if result:
         print(result, file=args.output)
     else: