pyloxi: generate virtual class unpack methods

This is done completely from the IR now.
diff --git a/py_gen/templates/_ofclass.py b/py_gen/templates/_ofclass.py
index 610e5a8..c84ceea 100644
--- a/py_gen/templates/_ofclass.py
+++ b/py_gen/templates/_ofclass.py
@@ -42,3 +42,10 @@
 
     def pretty_print(self, q):
 :: include('_pretty_print.py', ofclass=ofclass)
+
+:: # 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/_virtual_ofclass.py b/py_gen/templates/_virtual_ofclass.py
index 29f01f7..1e5dcc2 100644
--- a/py_gen/templates/_virtual_ofclass.py
+++ b/py_gen/templates/_virtual_ofclass.py
@@ -1,3 +1,23 @@
+:: 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}):
-    pass
+    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/action.py b/py_gen/templates/action.py
index 2f1d020..9f80243 100644
--- a/py_gen/templates/action.py
+++ b/py_gen/templates/action.py
@@ -43,9 +43,7 @@
 
 def unpack_list(reader):
     def deserializer(reader, typ):
-        parser = parsers.get(typ)
-        if not parser: raise loxi.ProtocolError("unknown action type %d" % typ)
-        return parser(reader)
+        return action.unpack(reader)
     return loxi.generic_util.unpack_list_tlv16(reader, deserializer)
 
 :: for ofclass in ofclasses:
@@ -56,46 +54,3 @@
 :: #endif
 
 :: #endfor
-
-def parse_experimenter(reader):
-    experimenter, = reader.peek("!4xL")
-    if experimenter == 0x005c16c7: # Big Switch Networks
-        subtype, = reader.peek("!8xL")
-    elif experimenter == 0x00002320: # Nicira
-        subtype, = reader.peek("!8xH")
-    else:
-        raise loxi.ProtocolError("unexpected experimenter id %#x" % experimenter)
-
-    if subtype in experimenter_parsers[experimenter]:
-        return experimenter_parsers[experimenter][subtype](reader)
-    else:
-        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(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)
-:: if len(v) == 1:
-    ${k} : ${v[0].pyname}.unpack,
-:: else:
-    ${k} : parse_${k[12:].lower()},
-:: #endif
-:: #endfor
-}
-
-:: 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)
-experimenter_parsers = {
-:: for (experimenter, v) in grouped:
-    ${experimenter} : {
-:: for ofclass in v:
-        ${ofclass.member_by_name('subtype').value}: ${ofclass.pyname}.unpack,
-:: #endfor
-    },
-:: #endfor
-}
diff --git a/py_gen/templates/common.py b/py_gen/templates/common.py
index ddffdf7..6569c77 100644
--- a/py_gen/templates/common.py
+++ b/py_gen/templates/common.py
@@ -54,10 +54,7 @@
 
 def unpack_list_queue_prop(reader):
     def deserializer(reader, typ):
-        if typ == const.OFPQT_MIN_RATE:
-            return queue_prop_min_rate.unpack(reader)
-        else:
-            raise loxi.ProtocolError("unknown queue prop %d" % typ)
+        return queue_prop.unpack(reader)
     return loxi.generic_util.unpack_list_tlv16(reader, deserializer)
 
 def unpack_list_packet_queue(reader):
@@ -68,9 +65,9 @@
 
 def unpack_list_hello_elem(reader):
     def deserializer(reader, typ):
-        if typ == const.OFPHET_VERSIONBITMAP:
-            return hello_elem_versionbitmap.unpack(reader)
-        else:
+        try:
+            return hello_elem.unpack(reader)
+        except loxi.ProtocolError:
             return None
     return [x for x in loxi.generic_util.unpack_list_tlv16(reader, deserializer) if x != None]
 
diff --git a/py_gen/templates/generic_util.py b/py_gen/templates/generic_util.py
index 28d3b5f..cd62057 100644
--- a/py_gen/templates/generic_util.py
+++ b/py_gen/templates/generic_util.py
@@ -97,11 +97,11 @@
         self.offset += len(buf)
         return str(buf)
 
-    def peek(self, fmt):
+    def peek(self, fmt, offset=0):
         st = struct.Struct(fmt)
-        if self.offset + st.size > len(self.buf):
+        if self.offset + offset + st.size > len(self.buf):
             raise loxi.ProtocolError("Buffer too short")
-        result = st.unpack_from(self.buf, self.offset)
+        result = st.unpack_from(self.buf, self.offset + offset)
         return result
 
     def skip(self, length):
diff --git a/py_gen/templates/instruction.py b/py_gen/templates/instruction.py
index 21ab84a..9842f95 100644
--- a/py_gen/templates/instruction.py
+++ b/py_gen/templates/instruction.py
@@ -40,9 +40,7 @@
 
 def unpack_list(reader):
     def deserializer(reader, typ):
-        parser = parsers.get(typ)
-        if not parser: raise loxi.ProtocolError("unknown instruction type %d" % typ)
-        return parser(reader)
+        return instruction.unpack(reader)
     return loxi.generic_util.unpack_list_tlv16(reader, deserializer)
 
 :: for ofclass in ofclasses:
@@ -53,44 +51,3 @@
 :: #endif
 
 :: #endfor
-
-def parse_experimenter(reader):
-    experimenter, = reader.peek("!4xL")
-    if experimenter == 0x005c16c7: # Big Switch Networks
-        subtype, = reader.peek("!8xL")
-    else:
-        raise loxi.ProtocolError("unexpected experimenter id %#x" % experimenter)
-
-    if subtype in experimenter_parsers[experimenter]:
-        return experimenter_parsers[experimenter][subtype](reader)
-    else:
-        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(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)
-:: if len(v) == 1 and k != 'const.OFPIT_EXPERIMENTER':
-    ${k} : ${v[0].pyname}.unpack,
-:: else:
-    ${k} : parse_${k[12:].lower()},
-:: #endif
-:: #endfor
-}
-
-:: 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)
-experimenter_parsers = {
-:: for (experimenter, v) in grouped:
-    ${experimenter} : {
-:: for ofclass in v:
-        ${ofclass.member_by_name('subtype').value}: ${ofclass.pyname}.unpack,
-:: #endfor
-    },
-:: #endfor
-}
diff --git a/py_gen/templates/message.py b/py_gen/templates/message.py
index 9307676..11e302f 100644
--- a/py_gen/templates/message.py
+++ b/py_gen/templates/message.py
@@ -68,235 +68,4 @@
         raise loxi.ProtocolError("wrong OpenFlow version (expected %d, got %d)" % (const.OFP_VERSION, msg_ver))
     if len(buf) != msg_len:
         raise loxi.ProtocolError("incorrect message size")
-    if msg_type in parsers:
-        return parsers[msg_type](buf)
-    else:
-        raise loxi.ProtocolError("unexpected message type")
-
-def parse_error(buf):
-    if len(buf) < 8 + 2:
-        raise loxi.ProtocolError("message too short")
-    err_type, = struct.unpack_from("!H", buf, 8)
-    if err_type in error_msg_parsers:
-        return error_msg_parsers[err_type](buf)
-    else:
-        raise loxi.ProtocolError("unexpected error type %u" % err_type)
-
-def parse_flow_mod(buf):
-:: if version == OFVersions.VERSION_1_0:
-:: offset = 57
-:: elif version >= OFVersions.VERSION_1_1:
-:: offset = 25
-:: #endif
-    if len(buf) < ${offset} + 1:
-        raise loxi.ProtocolError("message too short")
-    # Technically uint16_t for OF 1.0
-    cmd, = struct.unpack_from("!B", buf, ${offset})
-    if cmd in flow_mod_parsers:
-        return flow_mod_parsers[cmd](buf)
-    else:
-        raise loxi.ProtocolError("unexpected flow mod cmd %u" % cmd)
-
-:: if version >= OFVersions.VERSION_1_0:
-def parse_group_mod(buf):
-:: offset = 8
-    if len(buf) < ${offset} + 2:
-        raise loxi.ProtocolError("message too short")
-    cmd, = struct.unpack_from("!H", buf, ${offset})
-    if cmd in flow_mod_parsers:
-        return group_mod_parsers[cmd](buf)
-    else:
-        raise loxi.ProtocolError("unexpected group mod cmd %u" % cmd)
-:: #endif
-
-def parse_stats_reply(buf):
-    if len(buf) < 8 + 2:
-        raise loxi.ProtocolError("message too short")
-    stats_type, = struct.unpack_from("!H", buf, 8)
-    if stats_type in stats_reply_parsers:
-        return stats_reply_parsers[stats_type](buf)
-    else:
-        raise loxi.ProtocolError("unexpected stats type %u" % stats_type)
-
-def parse_stats_request(buf):
-    if len(buf) < 8 + 2:
-        raise loxi.ProtocolError("message too short")
-    stats_type, = struct.unpack_from("!H", buf, 8)
-    if stats_type in stats_request_parsers:
-        return stats_request_parsers[stats_type](buf)
-    else:
-        raise loxi.ProtocolError("unexpected stats type %u" % stats_type)
-
-def parse_experimenter_stats_request(buf):
-    if len(buf) < 24:
-        raise loxi.ProtocolError("experimenter stats request message too short")
-
-    experimenter, exp_type = struct.unpack_from("!LL", buf, 16)
-
-    if experimenter in experimenter_stats_request_parsers and \
-            exp_type in experimenter_stats_request_parsers[experimenter]:
-        return experimenter_stats_request_parsers[experimenter][exp_type](buf)
-    else:
-        raise loxi.ProtocolError("unexpected stats request experimenter %#x exp_type %#x" % (experimenter, exp_type))
-
-def parse_experimenter_stats_reply(buf):
-    if len(buf) < 24:
-        raise loxi.ProtocolError("experimenter stats reply message too short")
-
-    experimenter, exp_type = struct.unpack_from("!LL", buf, 16)
-
-    if experimenter in experimenter_stats_reply_parsers and \
-            exp_type in experimenter_stats_reply_parsers[experimenter]:
-        return experimenter_stats_reply_parsers[experimenter][exp_type](buf)
-    else:
-        raise loxi.ProtocolError("unexpected stats reply experimenter %#x exp_type %#x" % (experimenter, exp_type))
-
-def parse_experimenter(buf):
-    if len(buf) < 16:
-        raise loxi.ProtocolError("experimenter message too short")
-
-    experimenter, = struct.unpack_from("!L", buf, 8)
-    if experimenter == 0x005c16c7: # Big Switch Networks
-        subtype, = struct.unpack_from("!L", buf, 12)
-    elif experimenter == 0x00002320: # Nicira
-        subtype, = struct.unpack_from("!L", buf, 12)
-    else:
-        raise loxi.ProtocolError("unexpected experimenter id %#x" % experimenter)
-
-    if subtype in experimenter_parsers[experimenter]:
-        return experimenter_parsers[experimenter][subtype](buf)
-    else:
-        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(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)
-:: if len(v) == 1:
-    ${k} : ${v[0].pyname}.unpack,
-:: else:
-    ${k} : parse_${k[11:].lower()},
-:: #endif
-:: #endfor
-}
-
-error_msg_parsers = {
-    const.OFPET_HELLO_FAILED : hello_failed_error_msg.unpack,
-    const.OFPET_BAD_REQUEST : bad_request_error_msg.unpack,
-    const.OFPET_BAD_ACTION : bad_action_error_msg.unpack,
-    const.OFPET_FLOW_MOD_FAILED : flow_mod_failed_error_msg.unpack,
-    const.OFPET_PORT_MOD_FAILED : port_mod_failed_error_msg.unpack,
-    const.OFPET_QUEUE_OP_FAILED : queue_op_failed_error_msg.unpack,
-:: if version >= OFVersions.VERSION_1_1:
-    const.OFPET_BAD_INSTRUCTION : bad_instruction_error_msg.unpack,
-    const.OFPET_BAD_MATCH : bad_match_error_msg.unpack,
-    const.OFPET_GROUP_MOD_FAILED : group_mod_failed_error_msg.unpack,
-    const.OFPET_TABLE_MOD_FAILED : table_mod_failed_error_msg.unpack,
-    const.OFPET_SWITCH_CONFIG_FAILED : switch_config_failed_error_msg.unpack,
-:: #endif
-:: if version >= OFVersions.VERSION_1_2:
-    const.OFPET_ROLE_REQUEST_FAILED : role_request_failed_error_msg.unpack,
-    const.OFPET_EXPERIMENTER : experimenter_error_msg.unpack,
-:: #endif
-:: if version >= OFVersions.VERSION_1_3:
-    const.OFPET_METER_MOD_FAILED : meter_mod_failed_error_msg.unpack,
-    const.OFPET_TABLE_FEATURES_FAILED : table_features_failed_error_msg.unpack,
-:: #endif
-}
-
-flow_mod_parsers = {
-    const.OFPFC_ADD : flow_add.unpack,
-    const.OFPFC_MODIFY : flow_modify.unpack,
-    const.OFPFC_MODIFY_STRICT : flow_modify_strict.unpack,
-    const.OFPFC_DELETE : flow_delete.unpack,
-    const.OFPFC_DELETE_STRICT : flow_delete_strict.unpack,
-}
-
-:: if version >= OFVersions.VERSION_1_1:
-group_mod_parsers = {
-    const.OFPGC_ADD : group_add.unpack,
-    const.OFPGC_MODIFY : group_modify.unpack,
-    const.OFPGC_DELETE : group_delete.unpack,
-}
-:: #endif
-
-stats_reply_parsers = {
-    const.OFPST_DESC : desc_stats_reply.unpack,
-    const.OFPST_FLOW : flow_stats_reply.unpack,
-    const.OFPST_AGGREGATE : aggregate_stats_reply.unpack,
-    const.OFPST_TABLE : table_stats_reply.unpack,
-    const.OFPST_PORT : port_stats_reply.unpack,
-    const.OFPST_QUEUE : queue_stats_reply.unpack,
-    const.OFPST_EXPERIMENTER : parse_experimenter_stats_reply,
-:: if version >= OFVersions.VERSION_1_1:
-    const.OFPST_GROUP : group_stats_reply.unpack,
-    const.OFPST_GROUP_DESC : group_desc_stats_reply.unpack,
-:: #endif
-:: if version >= OFVersions.VERSION_1_2:
-    const.OFPST_GROUP_FEATURES : group_features_stats_reply.unpack,
-:: #endif
-:: if version >= OFVersions.VERSION_1_3:
-    const.OFPST_METER : meter_stats_reply.unpack,
-    const.OFPST_METER_CONFIG : meter_config_stats_reply.unpack,
-    const.OFPST_METER_FEATURES : meter_features_stats_reply.unpack,
-    const.OFPST_TABLE_FEATURES : table_features_stats_reply.unpack,
-    const.OFPST_PORT_DESC : port_desc_stats_reply.unpack,
-:: #endif
-}
-
-stats_request_parsers = {
-    const.OFPST_DESC : desc_stats_request.unpack,
-    const.OFPST_FLOW : flow_stats_request.unpack,
-    const.OFPST_AGGREGATE : aggregate_stats_request.unpack,
-    const.OFPST_TABLE : table_stats_request.unpack,
-    const.OFPST_PORT : port_stats_request.unpack,
-    const.OFPST_QUEUE : queue_stats_request.unpack,
-    const.OFPST_EXPERIMENTER : parse_experimenter_stats_request,
-:: if version >= OFVersions.VERSION_1_1:
-    const.OFPST_GROUP : group_stats_request.unpack,
-    const.OFPST_GROUP_DESC : group_desc_stats_request.unpack,
-:: #endif
-:: if version >= OFVersions.VERSION_1_2:
-    const.OFPST_GROUP_FEATURES : group_features_stats_request.unpack,
-:: #endif
-:: if version >= OFVersions.VERSION_1_3:
-    const.OFPST_METER : meter_stats_request.unpack,
-    const.OFPST_METER_CONFIG : meter_config_stats_request.unpack,
-    const.OFPST_METER_FEATURES : meter_features_stats_request.unpack,
-    const.OFPST_TABLE_FEATURES : table_features_stats_request.unpack,
-    const.OFPST_PORT_DESC : port_desc_stats_request.unpack,
-:: #endif
-}
-
-:: 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)
-experimenter_parsers = {
-:: for (experimenter, v) in grouped:
-    ${experimenter} : {
-:: for ofclass in v:
-        ${ofclass.member_by_name('subtype').value}: ${ofclass.pyname}.unpack,
-:: #endfor
-    },
-:: #endfor
-}
-
-experimenter_stats_request_parsers = {
-    0x005c16c7: {
-:: if version >= OFVersions.VERSION_1_3:
-        1: bsn_lacp_stats_request.unpack,
-:: #endif
-    },
-}
-
-experimenter_stats_reply_parsers = {
-    0x005c16c7: {
-:: if version >= OFVersions.VERSION_1_3:
-        1: bsn_lacp_stats_reply.unpack,
-:: #endif
-    },
-}
+    return message.unpack(loxi.generic_util.OFReader(buf))
diff --git a/py_gen/templates/meter_band.py b/py_gen/templates/meter_band.py
index ef73978..38607e7 100644
--- a/py_gen/templates/meter_band.py
+++ b/py_gen/templates/meter_band.py
@@ -39,9 +39,7 @@
 
 def unpack_list(reader):
     def deserializer(reader, typ):
-        parser = parsers.get(typ)
-        if not parser: raise loxi.ProtocolError("unknown meter band type %d" % typ)
-        return parser(reader)
+        return meter_band.unpack(reader)
     return loxi.generic_util.unpack_list_tlv16(reader, deserializer)
 
 :: for ofclass in ofclasses:
@@ -52,18 +50,3 @@
 :: #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(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)
-:: if len(v) == 1:
-    ${k} : ${v[0].pyname}.unpack,
-:: else:
-    ${k} : parse_${k[12:].lower()},
-:: #endif
-:: #endfor
-}
diff --git a/py_gen/templates/oxm.py b/py_gen/templates/oxm.py
index 4adfab0..71bf7df 100644
--- a/py_gen/templates/oxm.py
+++ b/py_gen/templates/oxm.py
@@ -38,12 +38,7 @@
 import loxi
 
 def unpack(reader):
-    type_len, = reader.peek('!L')
-    if type_len in parsers:
-        return parsers[type_len](reader)
-    else:
-        raise loxi.ProtocolError("unknown OXM cls=%#x type=%#x masked=%d len=%d (%#x)" % \
-            ((type_len >> 16) & 0xffff, (type_len >> 9) & 0x7f, (type_len >> 8) & 1, type_len & 0xff, type_len))
+    return oxm.unpack(reader)
 
 def unpack_list(reader):
     return loxi.generic_util.unpack_list(reader, unpack)
@@ -56,11 +51,3 @@
 :: #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(concrete_ofclasses, key=key):
-    ${key(ofclass)} : ${ofclass.pyname}.unpack,
-:: #endfor
-}
diff --git a/py_gen/tests/of10.py b/py_gen/tests/of10.py
index 818f123..876926e 100644
--- a/py_gen/tests/of10.py
+++ b/py_gen/tests/of10.py
@@ -114,7 +114,7 @@
 
     def test_invalid_action_type(self):
         buf = '\xff\xfe\x00\x08\x00\x00\x00\x00'
-        with self.assertRaisesRegexp(ofp.ProtocolError, 'unknown action type'):
+        with self.assertRaisesRegexp(ofp.ProtocolError, 'unknown action subtype'):
             ofp.action.unpack_list(OFReader(buf))
 
 class TestConstants(unittest.TestCase):
diff --git a/py_gen/util.py b/py_gen/util.py
index b405441..2a0dc54 100644
--- a/py_gen/util.py
+++ b/py_gen/util.py
@@ -49,3 +49,10 @@
         if value == value2:
             return "const." + name
     return repr(value)
+
+def ancestors(ofclass):
+    r = []
+    while ofclass:
+        r.append(ofclass)
+        ofclass = ofclass.superclass
+    return r