loxi_ir: Moved to its own package

to group ir and related functionality. Also introduce a dedicated
frontend_ir model, and adds a unit test
diff --git a/loxi_ir/ir.py b/loxi_ir/ir.py
new file mode 100644
index 0000000..21f760c
--- /dev/null
+++ b/loxi_ir/ir.py
@@ -0,0 +1,412 @@
+# Copyright 2013, Big Switch Networks, Inc.
+#
+# LoxiGen is licensed under the Eclipse Public License, version 1.0 (EPL), with
+# the following special exception:
+#
+# LOXI Exception
+#
+# As a special exception to the terms of the EPL, you may distribute libraries
+# generated by LoxiGen (LoxiGen Libraries) under the terms of your choice, provided
+# that copyright and licensing notices generated by LoxiGen are not altered or removed
+# from the LoxiGen Libraries and the notice provided below is (i) included in
+# the LoxiGen Libraries, if distributed in source code form and (ii) included in any
+# documentation for the LoxiGen Libraries, if distributed in binary form.
+#
+# Notice: "Copyright 2013, Big Switch Networks, Inc. This library was generated by the LoxiGen Compiler."
+#
+# You may not use this file except in compliance with the EPL or LOXI Exception. You may obtain
+# a copy of the EPL at:
+#
+# http://www.eclipse.org/legal/epl-v10.html
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# EPL for the specific language governing permissions and limitations
+# under the EPL.
+
+from itertools import chain
+import logging
+import re
+import sys
+
+from collections import namedtuple, OrderedDict
+from generic_utils import find, memoize, OrderedSet
+from loxi_ir import ir_offset
+
+logger = logging.getLogger(__name__)
+
+# This module is intended to be imported like this: from loxi_ir import *
+# All public names are prefixed with 'OF'.
+__all__ = [
+    'OFVersion',
+    'OFProtocol',
+    'OFClass',
+    'OFUnifiedClass',
+    'OFDataMember',
+    'OFTypeMember',
+    'OFDiscriminatorMember',
+    'OFLengthMember',
+    'OFFieldLengthMember',
+    'OFPadMember',
+    'OFEnum',
+    'OFEnumEntry'
+]
+
+"""
+One version of the OpenFlow protocol
+@param version Official dotted version number (e.g., "1.0", "1.3")
+@param wire_version Integer wire version (1 for 1.0, 4 for 1.3)
+"""
+class OFVersion(namedtuple("OFVersion", ("version", "wire_version"))):
+    @property
+    @memoize
+    def constant(self):
+        """ return this version as an uppercase string suitable
+            for use as a c constant, e.g., "VERSION_1_3"
+        """
+        return self.constant_version(prefix="VERSION_")
+
+    @property
+    @memoize
+    def short_constant(self):
+        """ return this version as an uppercase string suitable
+            for use as a c constant, e.g., "OF_"
+        """
+        return self.constant_version(prefix="OF_")
+
+    def constant_version(self, prefix="VERSION_"):
+        return prefix + self.version.replace(".", "_")
+
+    def __repr__(self):
+        return "OFVersion(%s)" % self.version
+
+    def __str__(self):
+        return self.version
+
+    def __cmp__(self, other):
+        return cmp(self.wire_version, other.wire_version)
+
+"""
+One version of the OpenFlow protocol
+
+Combination of multiple OFInput objects.
+
+@param wire_version
+@param classes List of OFClass objects
+@param enums List of Enum objects
+"""
+class OFProtocol(namedtuple('OFProtocol', ['version', 'classes', 'enums'])):
+    def __init__(self, version, classes, enums):
+        super(OFProtocol, self).__init__(self, version, classes, enums)
+        assert version is None or isinstance(version, OFVersion)
+
+    def class_by_name(self, name):
+        return find(lambda ofclass: ofclass.name == name, self.classes)
+
+    def enum_by_name(self, name):
+        return find(lambda enum: enum.name == name, self.enums)
+
+"""
+An OpenFlow class
+
+All compound objects like messages, actions, instructions, etc are
+uniformly represented by this class.
+
+The members are in the same order as on the wire.
+
+@param name
+@param superclass_name of this classes' super class
+@param members List of *Member objects
+@param params optional dictionary of parameters
+"""
+class OFClass(namedtuple('OFClass', ['name', 'superclass', 'members', 'virtual', 'params', 'is_fixed_length', 'base_length'])):
+    def __init__(self, *a, **kw):
+        super(OFClass, self).__init__(self, *a, **kw)
+        # Back reference will be added by assignment
+        self.protocol = None
+
+    def member_by_name(self, name):
+        return find(lambda m: hasattr(m, "name") and m.name == name, self.members)
+
+    @property
+    def discriminator(self):
+        return find(lambda m: type(m) == OFDiscriminatorMember, self.members)
+
+    def is_instanceof(self, super_class_name):
+        if self.name == super_class_name:
+            return True
+        elif self.superclass is None:
+            return False
+        else:
+            return self.superclass.is_instanceof(super_class_name)
+
+    def is_subclassof(self, super_class_name):
+        return self.name != super_class_name and self.is_instanceof(super_class_name)
+
+    @property
+    def is_message(self):
+        return self.is_instanceof("of_header")
+
+    @property
+    def is_oxm(self):
+        return self.is_instanceof("of_oxm")
+
+    @property
+    def is_action(self):
+        return self.is_instanceof("of_action")
+
+    @property
+    def is_action_id(self):
+        return self.is_instanceof("of_action_id")
+
+    @property
+    def is_instruction(self):
+        return self.is_instanceof("of_instruction")
+
+    def __hash__(self):
+        return hash((self.name, self.protocol.wire_version if self.protocol else None))
+
+    @property
+    def length(self):
+        if self.is_fixed_length:
+            return self.base_length
+        else:
+            raise Exception("Not a fixed length class: {}".self.name)
+
+""" one class unified across openflow versions. Keeps around a map version->versioned_class """
+class OFUnifiedClass(OFClass):
+    def __new__(cls, version_classes, *a, **kw):
+        return super(OFUnifiedClass, cls).__new__(cls, *a, **kw)
+
+    def __init__(self, version_classes, *a, **kw):
+        super(OFUnifiedClass, self).__init__(*a, **kw)
+        self.version_classes = version_classes
+
+    def class_by_version(version):
+        return self.version_classes[version]
+
+
+
+""" A mixin for member classes. Keeps around the back reference of_class (for assignment by
+    build_protocol, and additional methods shared across Members. """
+class MemberMixin(object):
+    def __init__(self, *a, **kw):
+        super(MemberMixin, self).__init__(*a, **kw)
+        # Back reference will be added by assignment in build_protocol below
+        self.of_class = None
+
+    @property
+    def length(self):
+        if self.is_fixed_length:
+            return self.base_length
+        else:
+            raise Exception("Not a fixed length member: {}.{} [{}]".format(
+                self.of_class.name,
+                self.name if hasattr("self", name) else "(unnnamed)",
+                type(self).__name__))
+
+"""
+Normal field
+
+@param name
+@param oftype C-like type string
+
+Example: packet_in.buffer_id
+"""
+class OFDataMember(namedtuple('OFDataMember', ['name', 'oftype', 'is_fixed_length', 'base_length', 'offset']), MemberMixin):
+    pass
+
+"""
+Field that declares that this is an abstract super-class and
+that the sub classes will be discriminated based on this field.
+E.g., 'type' is the discriminator member of the abstract superclass
+of_action.
+
+@param name
+"""
+class OFDiscriminatorMember (namedtuple('OFDiscriminatorMember', ['name', 'oftype', 'is_fixed_length', 'base_length', 'offset']), MemberMixin):
+    pass
+
+"""
+Field used to determine the type of an OpenFlow object
+
+@param name
+@param oftype C-like type string
+@param value Fixed type value
+
+Example: packet_in.type, flow_add._command
+"""
+class OFTypeMember (namedtuple('OFTypeMember', ['name', 'oftype', 'value', 'is_fixed_length', 'base_length', 'offset']), MemberMixin):
+    pass
+
+"""
+Field with the length of the containing object
+
+@param name
+@param oftype C-like type string
+
+Example: packet_in.length, action_output.len
+"""
+class OFLengthMember (namedtuple('OFLengthMember', ['name', 'oftype', 'is_fixed_length', 'base_length', 'offset']), MemberMixin):
+    pass
+
+"""
+Field with the length of another field in the containing object
+
+@param name
+@param oftype C-like type string
+@param field_name Peer field whose length this field contains
+
+Example: packet_out.actions_len (only usage)
+"""
+class OFFieldLengthMember (namedtuple('OFFieldLengthMember', ['name', 'oftype', 'field_name', 'is_fixed_length', 'base_length', 'offset']), MemberMixin):
+    pass
+
+"""
+Zero-filled padding
+
+@param length Length in bytes
+
+Example: packet_in.pad
+"""
+class OFPadMember (namedtuple('OFPadMember', ['pad_length', 'is_fixed_length', 'base_length', 'offset']), MemberMixin):
+    pass
+
+"""
+An OpenFlow enumeration
+
+All values are Python ints.
+
+@param name
+@param entries List of OFEnumEntry objects in input order
+@params dict of optional params. Currently defined:
+       - wire_type: the low_level type of the enum values (uint8,...)
+"""
+class OFEnum(namedtuple('OFEnum', ['name', 'entries', 'params'])):
+    def __init__(self, *a, **kw):
+        super(OFEnum, self).__init__(*a, **kw)
+        # Back reference will be added by assignment
+        self.protocol = None
+
+    @property
+    def values(self):
+        return [(e.name, e.value) for e in self.entries]
+
+    @property
+    def is_bitmask(self):
+        return "bitmask" in self.params and self.params['bitmask']
+
+    @property
+    def wire_type(self):
+        return self.params['wire_type'] if 'wire_type' in self.params else self.name
+
+class OFEnumEntry(namedtuple('OFEnumEntry', ['name', 'value', 'params'])):
+    def __init__(self, *a, **kw):
+        super(OFEnumEntry, self).__init__(*a, **kw)
+        # Back reference will be added by assignment
+        self.enum = None
+
+class RedefinedException(Exception):
+    pass
+
+class ClassNotFoundException(Exception):
+    pass
+
+class DependencyCycleException(Exception):
+    pass
+
+def build_protocol(version, ofinputs):
+    name_frontend_classes = OrderedDict()
+    name_frontend_enums = OrderedDict()
+
+    for ofinput in ofinputs:
+        for c in ofinput.classes:
+            name = c.name
+            if name in name_frontend_classes:
+                raise RedefinedException("Error parsing {}. Class {} redefined (already defined in {})"
+                        .format(ofinput.filename, name,
+                            name_frontend_classes[name][1].filename))
+            else:
+                name_frontend_classes[name] = (c, ofinput)
+        for e in ofinput.enums:
+            name = e.name
+            if name in name_frontend_enums:
+                raise RedefinedException("Error parsing {}. Enum {} redefined (already defined in {})"
+                        .format(ofinput.filename, name,
+                            name_frontend_enums[name][1].filename))
+            else:
+                name_frontend_enums[name] = (e, ofinput)
+
+    name_enums = {}
+    for fe, _ in name_frontend_enums.values():
+        entries = tuple(OFEnumEntry(name=e.name, value=e.value,
+                        params=e.params) for e in fe.entries)
+        enum = OFEnum(name=fe.name,
+                      entries=entries,
+                      params=fe.params)
+        for e in entries:
+            e.enum = enum
+        name_enums[enum.name] = enum
+
+    name_classes = OrderedDict()
+    build_touch_classes = OrderedSet()
+
+    def convert_member_properties(props):
+        return { name if name != "length" else "pad_length" : value for name, value in props.items() }
+
+    def build_member(of_class, fe_member, length_info):
+        ir_class = globals()[type(fe_member).__name__]
+        member = ir_class(offset = length_info.offset,
+                        base_length = length_info.base_length,
+                        is_fixed_length=length_info.is_fixed_length,
+                        **convert_member_properties(vars(fe_member)))
+        member.of_class = of_class
+        return member
+
+    def build_class(name):
+        if name in name_classes:
+            return name_classes[name]
+        if name in build_touch_classes:
+            raise DependencyCycleException( "Dependency cycle: {}"
+                    .format(" -> ".join(list(build_touch_classes) + [name])))
+        if not name in name_frontend_classes:
+            raise ClassNotFoundException("Class not found: {}".format(name))
+
+        build_touch_classes.add(name)
+
+        fe, _ = name_frontend_classes[name]
+
+        superclass = build_class(fe.superclass) if fe.superclass else None
+
+        # make sure members on which we depend are built first (for calc_length)
+        for m in fe.members:
+            if not hasattr(m, "oftype"):
+                continue
+            for m_name in re.sub(r'_t$', '', m.oftype), m.oftype:
+                logger.debug("Checking {}".format(m_name))
+                if m_name in name_frontend_classes:
+                    build_class(m_name)
+
+        base_length, is_fixed_length, member_lengths = \
+           ir_offset.calc_lengths(version, fe, name_classes, name_enums)
+
+        members = []
+        c = OFClass(name=fe.name, superclass=superclass,
+                members=members, virtual=fe.virtual, params=fe.params,
+                is_fixed_length=is_fixed_length, base_length=base_length)
+
+        members.extend( build_member(c, fe_member, member_lengths[fe_member])
+                  for fe_member in fe.members)
+
+        name_classes[name] = c
+        build_touch_classes.remove(name)
+        return c
+
+    for name in sorted(name_frontend_classes.keys()):
+        c = build_class(name)
+
+    protocol = OFProtocol(version=version, classes=tuple(name_classes.values()), enums=tuple(name_enums.values()))
+    for e in chain(protocol.classes, protocol.enums):
+        e.protocol = protocol
+    return protocol