vppapigen:  add parser support for enumflags

Type: improvement

Change-Id: I0f15862cc8399a4f7c8a81fe44ba8b27d8772278
Signed-off-by: Paul Vinciguerra <pvinci@vinciconsulting.com>
Signed-off-by: Ole Troan <ot@cisco.com>
(cherry picked from commit e15523297bb3905f2e0eef4272fc69a8a92463cc)
diff --git a/src/tools/vppapigen/vppapigen.py b/src/tools/vppapigen/vppapigen.py
index da00823..b80dd4d 100755
--- a/src/tools/vppapigen/vppapigen.py
+++ b/src/tools/vppapigen/vppapigen.py
@@ -58,6 +58,7 @@
         'define': 'DEFINE',
         'typedef': 'TYPEDEF',
         'enum': 'ENUM',
+        'enumflag': 'ENUMFLAG',
         'typeonly': 'TYPEONLY',
         'manual_print': 'MANUAL_PRINT',
         'manual_endian': 'MANUAL_ENDIAN',
@@ -182,7 +183,16 @@
     return vla
 
 
-class Service():
+class Processable:
+    type = "<Invalid>"
+
+    def process(self, result):  # -> Dict
+        result[self.type].append(self)
+
+
+class Service(Processable):
+    type = 'Service'
+
     def __init__(self, caller, reply, events=None, stream_message=None,
                  stream=False):
         self.caller = caller
@@ -192,10 +202,11 @@
         self.events = [] if events is None else events
 
 
-class Typedef():
+class Typedef(Processable):
+    type = 'Typedef'
+
     def __init__(self, name, flags, block):
         self.name = name
-        self.type = 'Typedef'
         self.flags = flags
         self.block = block
         self.crc = str(block).encode()
@@ -211,14 +222,18 @@
         self.vla = vla_is_last_check(name, block)
         vla_mark_length_field(self.block)
 
+    def process(self, result):
+        result['types'].append(self)
+
     def __repr__(self):
         return self.name + str(self.flags) + str(self.block)
 
 
-class Using():
+class Using(Processable):
+    type = 'Using'
+
     def __init__(self, name, flags, alias):
         self.name = name
-        self.type = 'Using'
         self.vla = False
         self.block = []
         self.manual_print = True
@@ -248,13 +263,17 @@
         self.crc = str(self.block).encode()
         global_type_add(name, self)
 
+    def process(self, result):  # -> Dict
+        result['types'].append(self)
+
     def __repr__(self):
         return self.name + str(self.alias)
 
 
-class Union():
+class Union(Processable):
+    type = 'Union'
+
     def __init__(self, name, flags, block):
-        self.type = 'Union'
         self.manual_print = False
         self.manual_endian = False
         self.name = name
@@ -271,14 +290,18 @@
 
         global_type_add(name, self)
 
+    def process(self, result):
+        result['types'].append(self)
+
     def __repr__(self):
         return str(self.block)
 
 
-class Define():
+class Define(Processable):
+    type = 'Define'
+
     def __init__(self, name, flags, block):
         self.name = name
-        self.type = 'Define'
         self.flags = flags
         self.block = block
         self.dont_trace = False
@@ -313,16 +336,31 @@
 
         self.crc = str(block).encode()
 
+    def autoreply_block(self, name, parent):
+        block = [Field('u32', 'context'),
+                 Field('i32', 'retval')]
+        # inherit the parent's options
+        for k, v in parent.options.items():
+            block.append(Option(k, v))
+        return Define(name + '_reply', [], block)
+
+    def process(self, result):  # -> Dict
+        tname = self.__class__.__name__
+        result[tname].append(self)
+        if self.autoreply:
+            result[tname].append(self.autoreply_block(self.name, self))
+
     def __repr__(self):
         return self.name + str(self.flags) + str(self.block)
 
 
-class Enum():
+class Enum(Processable):
+    type = 'Enum'
+
     def __init__(self, name, block, enumtype='u32'):
         self.name = name
         self.enumtype = enumtype
         self.vla = False
-        self.type = 'Enum'
         self.manual_print = False
 
         count = 0
@@ -350,12 +388,30 @@
         self.crc = str(block3).encode()
         global_type_add(name, self)
 
+    def process(self, result):
+        result['types'].append(self)
+
     def __repr__(self):
         return self.name + str(self.block)
 
 
-class Import():
+class EnumFlag(Enum):
+    type = 'EnumFlag'
+
+    def __init__(self, name, block, enumtype='u32'):
+        super(EnumFlag, self).__init__(name, block, enumtype)
+
+        for b in self.block:
+            if bin(b[1])[2:].count("1") > 1:
+                raise TypeError("%s is not a flag enum.  No element in a "
+                                "flag enum may have more than a "
+                                "single bit set." % self.name)
+
+
+class Import(Processable):
+    type = 'Import'
     _initialized = False
+
     def __new__(cls, *args, **kwargs):
         if args[0] not in seen_imports:
             instance = super().__new__(cls)
@@ -383,13 +439,17 @@
         return self.filename
 
 
-class Option():
+class Option(Processable):
+    type = 'Option'
+
     def __init__(self, option, value=None):
-        self.type = 'Option'
         self.option = option
         self.value = value
         self.crc = str(option).encode()
 
+    def process(self, result):  # -> Dict
+        result[self.type][self.option] = self.value
+
     def __repr__(self):
         return str(self.option)
 
@@ -397,9 +457,10 @@
         return self.option[index]
 
 
-class Array():
+class Array(Processable):
+    type = 'Array'
+
     def __init__(self, fieldtype, name, length, modern_vla=False):
-        self.type = 'Array'
         self.fieldtype = fieldtype
         self.fieldname = name
         self.modern_vla = modern_vla
@@ -417,9 +478,12 @@
                     self.lengthfield])
 
 
-class Field():
+class Field(Processable):
+    type = 'Field'
+
     def __init__(self, fieldtype, name, limit=None):
-        self.type = 'Field'
+        # limit field has been expanded to an options dict.
+
         self.fieldtype = fieldtype
         self.is_lengthfield = False
 
@@ -437,18 +501,28 @@
         return str([self.fieldtype, self.fieldname])
 
 
-class Counter():
+class Counter(Processable):
+    type = 'Counter'
+
     def __init__(self, path, counter):
-        self.type = 'Counter'
         self.name = path
         self.block = counter
 
+    def process(self, result):  # -> Dict
+        result['Counters'].append(self)
 
-class Paths():
+
+class Paths(Processable):
+    type = 'Paths'
+
     def __init__(self, pathset):
-        self.type = 'Paths'
         self.paths = pathset
 
+    def __repr__(self):
+        return "%s(paths=%s)" % (
+            self.__class__.__name__, self.paths
+        )
+
 
 class Coord(object):
     """ Coordinates of a syntactic element. Consists of:
@@ -523,6 +597,7 @@
                 | option
                 | import
                 | enum
+                | enumflag
                 | union
                 | service
                 | paths
@@ -643,6 +718,17 @@
         else:
             p[0] = Enum(p[2], p[4])
 
+    def p_enumflag(self, p):
+        '''enumflag : ENUMFLAG ID '{' enum_statements '}' ';' '''
+        p[0] = EnumFlag(p[2], p[4])
+
+    def p_enumflag_type(self, p):
+        ''' enumflag : ENUMFLAG ID ':' enum_size '{' enum_statements '}' ';' '''  # noqa : E502
+        if len(p) == 9:
+            p[0] = EnumFlag(p[2], p[6], enumtype=p[4])
+        else:
+            p[0] = EnumFlag(p[2], p[4])
+
     def p_enum_size(self, p):
         ''' enum_size : U8
                       | U16
@@ -902,14 +988,6 @@
                 print('File not found: {}'.format(filename), file=sys.stderr)
                 sys.exit(2)
 
-    def autoreply_block(self, name, parent):
-        block = [Field('u32', 'context'),
-                 Field('i32', 'retval')]
-        # inherhit the parent's options
-        for k, v in parent.options.items():
-            block.append(Option(k, v))
-        return Define(name + '_reply', [], block)
-
     def process(self, objs):
         s = {}
         s['Option'] = {}
@@ -921,32 +999,17 @@
         s['Paths'] = []
         crc = 0
         for o in objs:
-            tname = o.__class__.__name__
             try:
                 crc = binascii.crc32(o.crc, crc) & 0xffffffff
             except AttributeError:
                 pass
-            if isinstance(o, Define):
-                s[tname].append(o)
-                if o.autoreply:
-                    s[tname].append(self.autoreply_block(o.name, o))
-            elif isinstance(o, Option):
-                s[tname][o.option] = o.value
-            elif type(o) is list:
+
+            if type(o) is list:
                 for o2 in o:
                     if isinstance(o2, Service):
-                        s['Service'].append(o2)
-            elif isinstance(o, (Enum, Typedef, Union, Using)):
-                s['types'].append(o)
-            elif isinstance(o, Counter):
-                s['Counters'].append(o)
-            elif isinstance(o, Paths):
-                s['Paths'].append(o)
+                        o2.process(s)
             else:
-                if tname not in s:
-                    raise ValueError('Unknown class type: {} {}'
-                                     .format(tname, o))
-                s[tname].append(o)
+                o.process(s)
 
         msgs = {d.name: d for d in s['Define']}
         svcs = {s.caller: s for s in s['Service']}
@@ -1021,7 +1084,7 @@
 
         return s
 
-    def process_imports(self, objs, in_import, result):
+    def process_imports(self, objs, in_import, result):  # -> List
         for o in objs:
             # Only allow the following object types from imported file
             if in_import and not isinstance(o, (Enum, Import, Typedef,