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/codegen.py b/py_gen/codegen.py
index 4eafa48..f135f18 100644
--- a/py_gen/codegen.py
+++ b/py_gen/codegen.py
@@ -25,7 +25,7 @@
 # EPL for the specific language governing permissions and limitations
 # under the EPL.
 
-from collections import namedtuple
+from collections import defaultdict
 import loxi_globals
 import struct
 import template_utils
@@ -34,74 +34,74 @@
 import oftype
 from loxi_ir import *
 
-ofclasses_by_version = {}
+modules_by_version = {}
 
-# Return the name for the generated Python class
-def generate_pyname(cls):
-    if utils.class_is_action(cls):
-        return cls[10:]
-    elif utils.class_is_oxm(cls):
-        return cls[7:]
-    elif utils.class_is_meter_band(cls):
-        return cls[14:]
-    elif utils.class_is_instruction(cls):
-        return cls[15:]
-    else:
-        return cls[3:]
+# Map from inheritance root to module name
+roots = {
+    'of_header': 'message',
+    'of_action': 'action',
+    'of_oxm': 'oxm',
+    'of_instruction': 'instruction',
+    'of_meter_band': 'meter_band',
+}
+
+# Return the module and class names for the generated Python class
+def generate_pyname(ofclass):
+    for root, module_name in roots.items():
+        if ofclass.name == root:
+            return module_name, module_name
+        elif ofclass.is_instanceof(root):
+            if root == 'of_header':
+                # The input files don't prefix message names
+                return module_name, ofclass.name[3:]
+            else:
+                return module_name, ofclass.name[len(root)+1:]
+    return 'common', ofclass.name[3:]
 
 # Create intermediate representation, extended from the LOXI IR
 def build_ofclasses(version):
-    ofclasses = []
+    modules = defaultdict(list)
     for ofclass in loxi_globals.ir[version].classes:
-        if ofclass.virtual:
-            continue
-
-        ofclass.pyname = generate_pyname(ofclass.name)
-        ofclasses.append(ofclass)
-
-    return ofclasses
+        module_name, ofclass.pyname = generate_pyname(ofclass)
+        modules[module_name].append(ofclass)
+    return modules
 
 def generate_init(out, name, version):
     util.render_template(out, 'init.py', version=version)
 
 def generate_action(out, name, version):
-    ofclasses = [x for x in ofclasses_by_version[version]
-                 if utils.class_is_action(x.name)]
-    util.render_template(out, 'action.py', ofclasses=ofclasses, version=version)
+    util.render_template(out, 'action.py',
+                         ofclasses=modules_by_version[version]['action'],
+                         version=version)
 
 def generate_oxm(out, name, version):
-    ofclasses = [x for x in ofclasses_by_version[version]
-                 if utils.class_is_oxm(x.name)]
-    util.render_template(out, 'oxm.py', ofclasses=ofclasses, version=version)
+    util.render_template(out, 'oxm.py',
+                         ofclasses=modules_by_version[version]['oxm'],
+                         version=version)
 
 def generate_common(out, name, version):
-    ofclasses = [x for x in ofclasses_by_version[version]
-                 if not utils.class_is_message(x.name)
-                    and not utils.class_is_action(x.name)
-                    and not utils.class_is_instruction(x.name)
-                    and not utils.class_is_meter_band(x.name)
-                    and not utils.class_is_oxm(x.name)
-                    and not utils.class_is_list(x.name)]
-    util.render_template(out, 'common.py', ofclasses=ofclasses, version=version)
+    util.render_template(out, 'common.py',
+                         ofclasses=modules_by_version[version]['common'],
+                         version=version)
 
 def generate_const(out, name, version):
     util.render_template(out, 'const.py', version=version,
                          enums=loxi_globals.ir[version].enums)
 
 def generate_instruction(out, name, version):
-    ofclasses = [x for x in ofclasses_by_version[version]
-                 if utils.class_is_instruction(x.name)]
-    util.render_template(out, 'instruction.py', ofclasses=ofclasses, version=version)
+    util.render_template(out, 'instruction.py',
+                         ofclasses=modules_by_version[version]['instruction'],
+                         version=version)
 
 def generate_message(out, name, version):
-    ofclasses = [x for x in ofclasses_by_version[version]
-                 if utils.class_is_message(x.name)]
-    util.render_template(out, 'message.py', ofclasses=ofclasses, version=version)
+    util.render_template(out, 'message.py',
+                         ofclasses=modules_by_version[version]['message'],
+                         version=version)
 
 def generate_meter_band(out, name, version):
-    ofclasses = [x for x in ofclasses_by_version[version]
-                 if utils.class_is_meter_band(x.name)]
-    util.render_template(out, 'meter_band.py', ofclasses=ofclasses, version=version)
+    util.render_template(out, 'meter_band.py',
+                         ofclasses=modules_by_version[version]['meter_band'],
+                         version=version)
 
 def generate_pp(out, name, version):
     util.render_template(out, 'pp.py')
@@ -111,4 +111,4 @@
 
 def init():
     for version in loxi_globals.OFVersions.target_versions:
-        ofclasses_by_version[version] = build_ofclasses(version)
+        modules_by_version[version] = build_ofclasses(version)