pyloxi: parse unknown subclasses as the superclass

Previously we would throw an exception if we encountered a (for example)
experimenter action subclass that we didn't know existed. This was fine for
OFTest but causes problems for other tools that use pyloxi.

This change generates a full set of pack/unpack/pretty_print functions for
virtual classes. If a discriminator field contains a subtype we don't know
about, the virtual class is instantiated instead.

This also removes the special cases around unknown hello elements.
diff --git a/py_gen/templates/_ofclass.py b/py_gen/templates/_ofclass.py
index f902ae8..dfcd1fc 100644
--- a/py_gen/templates/_ofclass.py
+++ b/py_gen/templates/_ofclass.py
@@ -1,9 +1,19 @@
 :: superclass_pyname = ofclass.superclass.pyname if ofclass.superclass else "loxi.OFObject"
 :: from loxi_ir import *
 :: import py_gen.oftype
+:: import py_gen.util as util
 :: type_members = [m for m in ofclass.members if type(m) == OFTypeMember]
-:: normal_members = [m for m in ofclass.members if type(m) == OFDataMember]
+:: normal_members = [m for m in ofclass.members if type(m) == OFDataMember or
+::                                                 type(m) == OFDiscriminatorMember]
+:: if ofclass.virtual:
+:: discriminator_fmts = { 1: "B", 2: "!H", 4: "!L" }
+:: discriminator_fmt = discriminator_fmts[ofclass.discriminator.length]
+:: #endif
 class ${ofclass.pyname}(${superclass_pyname}):
+:: if ofclass.virtual:
+    subtypes = {}
+
+:: #endif
 :: for m in type_members:
     ${m.name} = ${m.value}
 :: #endfor
@@ -29,6 +39,13 @@
 
     @staticmethod
     def unpack(reader):
+:: if ofclass.virtual:
+        subtype, = reader.peek(${repr(discriminator_fmt)}, ${ofclass.discriminator.offset})
+        subclass = ${ofclass.pyname}.subtypes.get(subtype)
+        if subclass:
+            return subclass.unpack(reader)
+
+:: #endif
         obj = ${ofclass.pyname}()
 :: include("_unpack.py", ofclass=ofclass)
         return obj
diff --git a/py_gen/templates/_unpack.py b/py_gen/templates/_unpack.py
index 81f74c7..cce9de3 100644
--- a/py_gen/templates/_unpack.py
+++ b/py_gen/templates/_unpack.py
@@ -42,7 +42,7 @@
 ::     elif type(m) == OFTypeMember:
         _${m.name} = ${gen_unpack_expr(m.oftype, 'reader', version=version)}
         assert(_${m.name} == ${m.value})
-::     elif type(m) == OFDataMember:
+::     elif type(m) == OFDataMember or type(m) == OFDiscriminatorMember:
 ::         if m.name in field_length_members:
 ::             reader_expr = 'reader.slice(_%s)' % field_length_members[m.name].name
 ::         else:
diff --git a/py_gen/templates/_virtual_ofclass.py b/py_gen/templates/_virtual_ofclass.py
deleted file mode 100644
index 1e5dcc2..0000000
--- a/py_gen/templates/_virtual_ofclass.py
+++ /dev/null
@@ -1,23 +0,0 @@
-:: import py_gen.util as util
-:: superclass_pyname = ofclass.superclass.pyname if ofclass.superclass else "loxi.OFObject"
-:: fmts = { 1: "B", 2: "!H", 4: "!L" }
-:: fmt = fmts[ofclass.discriminator.length]
-:: trail = ' '.join([x.pyname for x in util.ancestors(ofclass)])
-class ${ofclass.pyname}(${superclass_pyname}):
-    subtypes = {}
-
-    @staticmethod
-    def unpack(reader):
-        subtype, = reader.peek(${repr(fmt)}, ${ofclass.discriminator.offset})
-        try:
-            subclass = ${ofclass.pyname}.subtypes[subtype]
-        except KeyError:
-            raise loxi.ProtocolError("unknown ${trail} subtype %#x" % subtype)
-        return subclass.unpack(reader)
-
-:: # Register with our superclass
-:: if ofclass.superclass:
-:: type_field_name = ofclass.superclass.discriminator.name
-:: type_value = ofclass.member_by_name(type_field_name).value
-${superclass_pyname}.subtypes[${type_value}] = ${ofclass.pyname}
-:: #endif
diff --git a/py_gen/templates/module.py b/py_gen/templates/module.py
index 02f0002..dfe23e8 100644
--- a/py_gen/templates/module.py
+++ b/py_gen/templates/module.py
@@ -52,11 +52,7 @@
 import loxi.generic_util
 
 :: for ofclass in ofclasses:
-:: if ofclass.virtual:
-:: include('_virtual_ofclass.py', ofclass=ofclass)
-:: else:
 :: include('_ofclass.py', ofclass=ofclass)
-:: #endif
 
 :: #endfor
 
diff --git a/py_gen/templates/util.py b/py_gen/templates/util.py
index d690939..85181dc 100644
--- a/py_gen/templates/util.py
+++ b/py_gen/templates/util.py
@@ -173,16 +173,6 @@
         x >>= 1
     return value
 
-def unpack_list_hello_elem(reader):
-    def deserializer(reader):
-        typ, length, = reader.peek('!HH')
-        reader = reader.slice(length)
-        try:
-            return common.hello_elem.unpack(reader)
-        except loxi.ProtocolError:
-            return None
-    return [x for x in loxi.generic_util.unpack_list(reader, deserializer) if x != None]
-
 def pack_checksum_128(value):
     return struct.pack("!QQ", (value >> 64) & MASK64, value & MASK64)