pyloxi: create inheritance hierarchy

Virtual classes are generated but currently only serve as superclasses.

Somehow this works without topologically sorting the classes.
diff --git a/py_gen/templates/_ofclass.py b/py_gen/templates/_ofclass.py
index 53d6d7a..610e5a8 100644
--- a/py_gen/templates/_ofclass.py
+++ b/py_gen/templates/_ofclass.py
@@ -1,8 +1,9 @@
+:: superclass_pyname = ofclass.superclass.pyname if ofclass.superclass else "loxi.OFObject"
 :: from loxi_ir import *
 :: import py_gen.oftype
 :: type_members = [m for m in ofclass.members if type(m) == OFTypeMember]
 :: normal_members = [m for m in ofclass.members if type(m) == OFDataMember]
-class ${ofclass.pyname}(${superclass}):
+class ${ofclass.pyname}(${superclass_pyname}):
 :: for m in type_members:
     ${m.name} = ${m.value}
 :: #endfor
@@ -39,12 +40,5 @@
 :: #endfor
         return True
 
-    def __ne__(self, other):
-        return not self.__eq__(other)
-
-    def show(self):
-        import loxi.pp
-        return loxi.pp.pp(self)
-
     def pretty_print(self, q):
 :: include('_pretty_print.py', ofclass=ofclass)
diff --git a/py_gen/templates/_virtual_ofclass.py b/py_gen/templates/_virtual_ofclass.py
new file mode 100644
index 0000000..29f01f7
--- /dev/null
+++ b/py_gen/templates/_virtual_ofclass.py
@@ -0,0 +1,3 @@
+:: superclass_pyname = ofclass.superclass.pyname if ofclass.superclass else "loxi.OFObject"
+class ${ofclass.pyname}(${superclass_pyname}):
+    pass
diff --git a/py_gen/templates/action.py b/py_gen/templates/action.py
index 77a1d9f..2f1d020 100644
--- a/py_gen/templates/action.py
+++ b/py_gen/templates/action.py
@@ -48,12 +48,12 @@
         return parser(reader)
     return loxi.generic_util.unpack_list_tlv16(reader, deserializer)
 
-class Action(object):
-    type = None # override in subclass
-    pass
-
 :: for ofclass in ofclasses:
-:: include('_ofclass.py', ofclass=ofclass, superclass="Action")
+:: if ofclass.virtual:
+:: include('_virtual_ofclass.py', ofclass=ofclass)
+:: else:
+:: include('_ofclass.py', ofclass=ofclass)
+:: #endif
 
 :: #endfor
 
@@ -72,8 +72,9 @@
         raise loxi.ProtocolError("unexpected BSN experimenter subtype %#x" % subtype)
 
 parsers = {
+:: concrete_ofclasses = [x for x in ofclasses if not x.virtual]
 :: sort_key = lambda x: x.member_by_name('type').value
-:: msgtype_groups = itertools.groupby(sorted(ofclasses, key=sort_key), sort_key)
+:: msgtype_groups = itertools.groupby(sorted(concrete_ofclasses, key=sort_key), sort_key)
 :: for (k, v) in msgtype_groups:
 :: k = util.constant_for_value(version, "ofp_action_type", k)
 :: v = list(v)
@@ -85,7 +86,7 @@
 :: #endfor
 }
 
-:: experimenter_ofclasses = [x for x in ofclasses if x.member_by_name('type').value == 0xffff]
+:: experimenter_ofclasses = [x for x in concrete_ofclasses if x.member_by_name('type').value == 0xffff]
 :: sort_key = lambda x: x.member_by_name('experimenter').value
 :: experimenter_ofclasses.sort(key=sort_key)
 :: grouped = itertools.groupby(experimenter_ofclasses, sort_key)
diff --git a/py_gen/templates/common.py b/py_gen/templates/common.py
index 76ae631..ddffdf7 100644
--- a/py_gen/templates/common.py
+++ b/py_gen/templates/common.py
@@ -90,7 +90,11 @@
     return loxi.generic_util.unpack_list(reader, wrapper)
 
 :: for ofclass in ofclasses:
-:: include('_ofclass.py', ofclass=ofclass, superclass="object")
+:: if ofclass.virtual:
+:: include('_virtual_ofclass.py', ofclass=ofclass)
+:: else:
+:: include('_ofclass.py', ofclass=ofclass)
+:: #endif
 
 :: #endfor
 
diff --git a/py_gen/templates/instruction.py b/py_gen/templates/instruction.py
index 5dfafab..21ab84a 100644
--- a/py_gen/templates/instruction.py
+++ b/py_gen/templates/instruction.py
@@ -45,12 +45,12 @@
         return parser(reader)
     return loxi.generic_util.unpack_list_tlv16(reader, deserializer)
 
-class Instruction(object):
-    type = None # override in subclass
-    pass
-
 :: for ofclass in ofclasses:
-:: include('_ofclass.py', ofclass=ofclass, superclass="Instruction")
+:: if ofclass.virtual:
+:: include('_virtual_ofclass.py', ofclass=ofclass)
+:: else:
+:: include('_ofclass.py', ofclass=ofclass)
+:: #endif
 
 :: #endfor
 
@@ -67,8 +67,9 @@
         raise loxi.ProtocolError("unexpected experimenter id %#x subtype %#x" % (experimenter, subtype))
 
 parsers = {
+:: concrete_ofclasses = [x for x in ofclasses if not x.virtual]
 :: sort_key = lambda x: x.member_by_name('type').value
-:: msgtype_groups = itertools.groupby(sorted(ofclasses, key=sort_key), sort_key)
+:: msgtype_groups = itertools.groupby(sorted(concrete_ofclasses, key=sort_key), sort_key)
 :: for (k, v) in msgtype_groups:
 :: k = util.constant_for_value(version, "ofp_instruction_type", k)
 :: v = list(v)
@@ -80,7 +81,7 @@
 :: #endfor
 }
 
-:: experimenter_ofclasses = [x for x in ofclasses if x.member_by_name('type').value == 0xffff]
+:: experimenter_ofclasses = [x for x in concrete_ofclasses if x.member_by_name('type').value == 0xffff]
 :: sort_key = lambda x: x.member_by_name('experimenter').value
 :: experimenter_ofclasses.sort(key=sort_key)
 :: grouped = itertools.groupby(experimenter_ofclasses, sort_key)
diff --git a/py_gen/templates/message.py b/py_gen/templates/message.py
index 2106ce7..9307676 100644
--- a/py_gen/templates/message.py
+++ b/py_gen/templates/message.py
@@ -48,13 +48,12 @@
 import util
 import loxi.generic_util
 
-class Message(object):
-    version = const.OFP_VERSION
-    type = None # override in subclass
-    xid = None
-
 :: for ofclass in ofclasses:
-:: include('_ofclass.py', ofclass=ofclass, superclass="Message")
+:: if ofclass.virtual:
+:: include('_virtual_ofclass.py', ofclass=ofclass)
+:: else:
+:: include('_ofclass.py', ofclass=ofclass)
+:: #endif
 
 :: #endfor
 
@@ -170,8 +169,9 @@
         raise loxi.ProtocolError("unexpected experimenter %#x subtype %#x" % (experimenter, subtype))
 
 parsers = {
+:: concrete_ofclasses = [x for x in ofclasses if not x.virtual]
 :: sort_key = lambda x: x.member_by_name('type').value
-:: msgtype_groups = itertools.groupby(sorted(ofclasses, key=sort_key), sort_key)
+:: msgtype_groups = itertools.groupby(sorted(concrete_ofclasses, key=sort_key), sort_key)
 :: for (k, v) in msgtype_groups:
 :: k = util.constant_for_value(version, "ofp_type", k)
 :: v = list(v)
@@ -271,7 +271,7 @@
 :: #endif
 }
 
-:: experimenter_ofclasses = [x for x in ofclasses if x.member_by_name('type').value == 4]
+:: experimenter_ofclasses = [x for x in concrete_ofclasses if x.member_by_name('type').value == 4]
 :: sort_key = lambda x: x.member_by_name('experimenter').value
 :: experimenter_ofclasses.sort(key=sort_key)
 :: grouped = itertools.groupby(experimenter_ofclasses, sort_key)
diff --git a/py_gen/templates/meter_band.py b/py_gen/templates/meter_band.py
index 3273314..ef73978 100644
--- a/py_gen/templates/meter_band.py
+++ b/py_gen/templates/meter_band.py
@@ -44,18 +44,19 @@
         return parser(reader)
     return loxi.generic_util.unpack_list_tlv16(reader, deserializer)
 
-class MeterBand(object):
-    type = None # override in subclass
-    pass
-
 :: for ofclass in ofclasses:
-:: include('_ofclass.py', ofclass=ofclass, superclass="MeterBand")
+:: if ofclass.virtual:
+:: include('_virtual_ofclass.py', ofclass=ofclass)
+:: else:
+:: include('_ofclass.py', ofclass=ofclass)
+:: #endif
 
 :: #endfor
 
 parsers = {
+:: concrete_ofclasses = [x for x in ofclasses if not x.virtual]
 :: sort_key = lambda x: x.member_by_name('type').value
-:: msgtype_groups = itertools.groupby(sorted(ofclasses, key=sort_key), sort_key)
+:: msgtype_groups = itertools.groupby(sorted(concrete_ofclasses, key=sort_key), sort_key)
 :: for (k, v) in msgtype_groups:
 :: k = util.constant_for_value(version, "ofp_meter_band_type", k)
 :: v = list(v)
diff --git a/py_gen/templates/oxm.py b/py_gen/templates/oxm.py
index 176a74c..4adfab0 100644
--- a/py_gen/templates/oxm.py
+++ b/py_gen/templates/oxm.py
@@ -48,18 +48,19 @@
 def unpack_list(reader):
     return loxi.generic_util.unpack_list(reader, unpack)
 
-class OXM(object):
-    type_len = None # override in subclass
-    pass
-
 :: for ofclass in ofclasses:
-:: include('_ofclass.py', ofclass=ofclass, superclass="OXM")
+:: if ofclass.virtual:
+:: include('_virtual_ofclass.py', ofclass=ofclass)
+:: else:
+:: include('_ofclass.py', ofclass=ofclass)
+:: #endif
 
 :: #endfor
 
 parsers = {
+:: concrete_ofclasses = [x for x in ofclasses if not x.virtual]
 :: key = lambda x: x.member_by_name('type_len').value
-:: for ofclass in sorted(ofclasses, key=key):
+:: for ofclass in sorted(concrete_ofclasses, key=key):
     ${key(ofclass)} : ${ofclass.pyname}.unpack,
 :: #endfor
 }
diff --git a/py_gen/templates/toplevel_init.py b/py_gen/templates/toplevel_init.py
index b170bd8..e5493a5 100644
--- a/py_gen/templates/toplevel_init.py
+++ b/py_gen/templates/toplevel_init.py
@@ -68,3 +68,17 @@
 
 def unimplemented(msg):
     raise Unimplemented(msg)
+
+class OFObject(object):
+    """
+    Superclass of all OpenFlow classes
+    """
+    def __init__(self, *args):
+        raise NotImplementedError("cannot instantiate abstract class")
+
+    def __ne__(self, other):
+        return not self.__eq__(other)
+
+    def show(self):
+        import loxi.pp
+        return loxi.pp.pp(self)