moved of_g to c_gen.of_g_legacy, introduced loxi_globals

as of_g includes loads of legacy data (and a representation that
is increasingly tailored to the c backend only), I've moved it to
c_gen.of_g. In its place, I have created loxi_globals, which gives
access to the list of versions and the IR.

Also loxi reorg: move command line processing to cmdline.py
diff --git a/c_gen/build_of_g.py b/c_gen/build_of_g.py
new file mode 100755
index 0000000..6e536fa
--- /dev/null
+++ b/c_gen/build_of_g.py
@@ -0,0 +1,559 @@
+# 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.
+
+import sys
+
+import re
+import string
+import os
+import glob
+import copy
+import collections
+import c_gen.of_g_legacy as of_g
+import c_gen.type_maps as type_maps
+import c_gen.loxi_utils_legacy as loxi_utils
+import loxi_globals
+import c_gen.identifiers as identifiers
+import pyparsing
+import loxi_front_end.parser as parser
+import c_gen.translation as translation
+import loxi_front_end.frontend as frontend
+from loxi_ir import *
+from generic_utils import *
+
+root_dir = os.path.dirname(os.path.realpath(__file__))
+
+versions = {}
+# TODO:  Put these in a class so they get documented
+
+## Dict indexed by version giving all info related to version
+#
+# This is local; after processing, the information is stored in
+# of_g variables.
+
+def add_class(wire_version, cls, members):
+    """
+    Process a class for the given version and update the unified
+    list of classes as needed.
+
+    @param wire_version The wire version for this class defn
+    @param cls The name of the class being added
+    @param members The list of members with offsets calculated
+    """
+    memid = 0
+
+    sig = loxi_utils.class_signature(members)
+    if cls in of_g.unified:
+        uc = of_g.unified[cls]
+        if wire_version in uc:
+            debug("Error adding %s to unified. Wire ver %d exists" %
+                  (cls, wire_version))
+            sys.exit(1)
+        uc[wire_version] = {}
+        # Check for a matching signature
+        for wver in uc:
+            if type(wver) != type(0): continue
+            if wver == wire_version: continue
+            if not "use_version" in uc[wver]:
+                if sig == loxi_utils.class_signature(uc[wver]["members"]):
+                    log("Matched %s, ver %d to ver %d" %
+                          (cls, wire_version, wver))
+                    # have a match with existing version
+                    uc[wire_version]["use_version"] = wver
+                    # What else to do?
+                    return
+    else:  # Haven't seen this entry before
+        log("Adding %s to unified list, ver %d" % (cls, wire_version))
+        of_g.unified[cls] = dict(union={})
+        uc = of_g.unified[cls]
+
+    # At this point, need to add members for this version
+    uc[wire_version] = dict(members = members)
+
+    # Per member processing:
+    #  Add to union list (I'm sure there's a better way)
+    #  Check if it's a list
+    union = uc["union"]
+    if not cls in of_g.ordered_members:
+        of_g.ordered_members[cls] = []
+    for member in members:
+        m_name = member["name"]
+        m_type = member["m_type"]
+        if m_name.find("pad") == 0:
+            continue
+        if m_name in union:
+            if not m_type == union[m_name]["m_type"]:
+                debug("ERROR:   CLASS: %s. VERSION %d. MEMBER: %s. TYPE: %s" %
+                      (cls, wire_version, m_name, m_type))
+                debug("    Type conflict adding member to unified set.")
+                debug("    Current union[%s]:" % m_name)
+                debug(union[m_name])
+                sys.exit(1)
+        else:
+            union[m_name] = dict(m_type=m_type, memid=memid)
+            memid += 1
+        if not m_name in of_g.ordered_members[cls]:
+            of_g.ordered_members[cls].append(m_name)
+
+def update_offset(cls, wire_version, name, offset, m_type):
+    """
+    Update (and return) the offset based on type.
+    @param cls The parent class
+    @param wire_version The wire version being processed
+    @param name The name of the data member
+    @param offset The current offset
+    @param m_type The type declaration being processed
+    @returns A pair (next_offset, len_update)  next_offset is the new offset
+    of the next object or -1 if this is a var-length object.  len_update
+    is the increment that should be added to the length.  Note that (for
+    of_match_v3) it is variable length, but it adds 8 bytes to the fixed
+    length of the object
+    If offset is already -1, do not update
+    Otherwise map to base type and count and update (if possible)
+    """
+    if offset < 0:    # Don't update offset once set to -1
+        return offset, 0
+
+    count, base_type = loxi_utils.type_dec_to_count_base(m_type)
+
+    len_update = 0
+    if base_type in of_g.of_mixed_types:
+        base_type = of_g.of_mixed_types[base_type][wire_version]
+
+    base_class = base_type[:-2]
+    if (base_class, wire_version) in of_g.is_fixed_length:
+        bytes = of_g.base_length[(base_class, wire_version)]
+    else:
+        if base_type == "of_match_v3_t":
+            # This is a special case: it has non-zero min length
+            # but is variable length
+            bytes = -1
+            len_update = 8
+        elif base_type in of_g.of_base_types:
+            bytes = of_g.of_base_types[base_type]["bytes"]
+        else:
+            print "UNKNOWN TYPE for %s %s: %s" % (cls, name, base_type)
+            log("UNKNOWN TYPE for %s %s: %s" % (cls, name, base_type))
+            bytes = -1
+
+    # If bytes
+    if bytes > 0:
+        len_update = count * bytes
+
+    if bytes == -1:
+        return -1, len_update
+
+    return offset + (count * bytes), len_update
+
+def calculate_offsets_and_lengths(ordered_classes, classes, wire_version):
+    """
+    Generate the offsets for fixed offset class members
+    Also calculate the class_sizes when possible.
+
+    @param classes The classes to process
+    @param wire_version The wire version for this set of classes
+
+    Updates global variables
+    """
+
+    lists = set()
+
+    # Generate offsets
+    for cls in ordered_classes:
+        fixed_offset = 0 # The last "good" offset seen
+        offset = 0
+        last_offset = 0
+        last_name = "-"
+        for member in classes[cls]:
+            m_type = member["m_type"]
+            name = member["name"]
+            if last_offset == -1:
+                if name == "pad":
+                    log("Skipping pad for special offset for %s" % cls)
+                else:
+                    log("SPECIAL OFS: Member %s (prev %s), class %s ver %d" %
+                          (name, last_name, cls, wire_version))
+                    if (((cls, name) in of_g.special_offsets) and
+                        (of_g.special_offsets[(cls, name)] != last_name)):
+                        debug("ERROR: special offset prev name changed")
+                        debug("  cls %s. name %s. version %d. was %s. now %s" %
+                              cls, name, wire_version,
+                              of_g.special_offsets[(cls, name)], last_name)
+                        sys.exit(1)
+                    of_g.special_offsets[(cls, name)] = last_name
+
+            member["offset"] = offset
+            if m_type.find("list(") == 0:
+                (list_name, base_type) = loxi_utils.list_name_extract(m_type)
+                lists.add(list_name)
+                member["m_type"] = list_name + "_t"
+                offset = -1
+            elif m_type.find("struct") == 0:
+                debug("ERROR found struct: %s.%s " % (cls, name))
+                sys.exit(1)
+            elif m_type == "octets":
+                log("offset gen skipping octets: %s.%s " % (cls, name))
+                offset = -1
+            else:
+                offset, len_update = update_offset(cls, wire_version, name,
+                                                  offset, m_type)
+                if offset != -1:
+                    fixed_offset = offset
+                else:
+                    fixed_offset += len_update
+                    log("offset is -1 for %s.%s version %d " %
+                        (cls, name, wire_version))
+            last_offset = offset
+            last_name = name
+        of_g.base_length[(cls, wire_version)] = fixed_offset
+        if (offset != -1):
+            of_g.is_fixed_length.add((cls, wire_version))
+    for list_type in lists:
+        classes[list_type] = []
+        of_g.ordered_classes[wire_version].append(list_type)
+        of_g.base_length[(list_type, wire_version)] = 0
+
+def order_and_assign_object_ids():
+    """
+    Order all classes and assign object ids to all classes.
+
+    This is done to promote a reasonable order of the objects, putting
+    messages first followed by non-messages.  No assumptions should be
+    made about the order, nor about contiguous numbering.  However, the
+    numbers should all be reasonably small allowing arrays indexed by
+    these enum values to be defined.
+    """
+
+    # Generate separate message and non-message ordered lists
+    for cls in of_g.unified:
+        if loxi_utils.class_is_message(cls):
+            of_g.ordered_messages.append(cls)
+        elif loxi_utils.class_is_list(cls):
+            of_g.ordered_list_objects.append(cls)
+        else:
+            of_g.ordered_non_messages.append(cls)
+
+    of_g.ordered_messages.sort()
+    of_g.ordered_pseudo_objects.sort()
+    of_g.ordered_non_messages.sort()
+    of_g.ordered_list_objects.sort()
+    of_g.standard_class_order.extend(of_g.ordered_messages)
+    of_g.standard_class_order.extend(of_g.ordered_non_messages)
+    of_g.standard_class_order.extend(of_g.ordered_list_objects)
+
+    # This includes pseudo classes for which most code is not generated
+    of_g.all_class_order.extend(of_g.ordered_messages)
+    of_g.all_class_order.extend(of_g.ordered_non_messages)
+    of_g.all_class_order.extend(of_g.ordered_list_objects)
+    of_g.all_class_order.extend(of_g.ordered_pseudo_objects)
+
+    # Assign object IDs
+    for cls in of_g.ordered_messages:
+        of_g.unified[cls]["object_id"] = of_g.object_id
+        of_g.object_id += 1
+    for cls in of_g.ordered_non_messages:
+        of_g.unified[cls]["object_id"] = of_g.object_id
+        of_g.object_id += 1
+    for cls in of_g.ordered_list_objects:
+        of_g.unified[cls]["object_id"] = of_g.object_id
+        of_g.object_id += 1
+    for cls in of_g.ordered_pseudo_objects:
+        of_g.unified[cls] = {}
+        of_g.unified[cls]["object_id"] = of_g.object_id
+        of_g.object_id += 1
+
+
+def initialize_versions():
+    """
+    Create an empty datastructure for each target version.
+    """
+
+    for version in loxi_globals.OFVersions.target_versions:
+        wire_version = version.wire_version
+        version_name = of_g.of_version_wire2name[wire_version]
+        of_g.wire_ver_map[wire_version] = version_name
+        versions[version_name] = dict(
+            version_name = version_name,
+            wire_version = wire_version,
+            classes = {})
+        of_g.ordered_classes[wire_version] = []
+
+    of_g.target_version_list = [ v.wire_version for v in loxi_globals.OFVersions.target_versions ]
+
+def build_ordered_classes():
+    """
+    Read in from files given on command line and update global state
+
+    @fixme Should select versions to support from command line
+    """
+
+    for version, protocol in loxi_globals.ir.items():
+        wire_version = version.wire_version
+        # Populate global state
+        version_name = of_g.of_version_wire2name[wire_version]
+
+        for ofclass in protocol.classes:
+            if ofclass.name in ("of_group_add", "of_group_modify", "of_group_delete"):
+                continue
+            of_g.ordered_classes[wire_version].append(ofclass.name)
+            legacy_members = []
+            pad_count = 0
+            for m in ofclass.members:
+                if type(m) == OFPadMember:
+                    m_name = 'pad%d' % pad_count
+                    if m_name == 'pad0': m_name = 'pad'
+                    legacy_members.append(dict(m_type='uint8_t[%d]' % m.length,
+                                               name=m_name))
+                    pad_count += 1
+                else:
+                    # HACK the C backend does not yet support of_oxm_t
+                    if m.oftype == 'of_oxm_t':
+                        m_type = 'of_octets_t'
+                    else:
+                        enum = find(lambda e: e.name == m.oftype, protocol.enums)
+                        if enum and "wire_type" in enum.params:
+                            m_type = enum.params["wire_type"]
+                        else:
+                            m_type = m.oftype
+                    legacy_members.append(dict(m_type=m_type, name=m.name))
+            versions[version_name]['classes'][ofclass.name] = legacy_members
+
+        for enum in protocol.enums:
+            for entry in enum.entries:
+                identifiers.add_identifier(
+                    translation.loxi_name(entry.name),
+                    entry.name, enum.name, entry.value, wire_version,
+                    of_g.identifiers, of_g.identifiers_by_group)
+
+def populate_type_maps():
+    """
+    Use the type members in the IR to fill out the legacy type_maps.
+    """
+
+    def split_inherited_cls(cls):
+        if cls == 'of_meter_band_stats': # HACK not a subtype of of_meter_band
+            return None, None
+        for parent in sorted(type_maps.inheritance_data.keys(), reverse=True):
+            if cls.startswith(parent):
+                return (parent, cls[len(parent)+1:])
+        return None, None
+
+    def find_experimenter(parent, cls):
+        for experimenter in sorted(of_g.experimenter_name_to_id.keys(), reverse=True):
+            prefix = parent + '_' + experimenter
+            if cls.startswith(prefix) and cls != prefix:
+                return experimenter
+        return None
+
+    def find_type_value(ofclass, m_name):
+        for m in ofclass.members:
+            if isinstance(m, OFTypeMember) and m.name == m_name:
+                return m.value
+        raise KeyError("ver=%d, cls=%s, m_name=%s" % (wire_version, cls, m_name))
+
+    # Most inheritance classes: actions, instructions, etc
+    for version, protocol in loxi_globals.ir.items():
+        wire_version = version.wire_version
+        for ofclass in protocol.classes:
+            cls = ofclass.name
+            parent, subcls = split_inherited_cls(cls)
+            if not (parent and subcls):
+                continue
+            if parent == 'of_oxm':
+                type_len = find_type_value(ofclass, 'type_len')
+                oxm_class = (type_len >> 16) & 0xffff
+                if oxm_class != 0x8000:
+                    # Do not include experimenter OXMs in the main table
+                    val = type_maps.invalid_type
+                else:
+                    val = (type_len >> 8) & 0xff
+            else:
+                val = find_type_value(ofclass, 'type')
+            type_maps.inheritance_data[parent][wire_version][subcls] = val
+
+            # Extensions (only actions for now)
+            experimenter = find_experimenter(parent, cls)
+            if parent == 'of_action' and experimenter:
+                val = find_type_value(ofclass, 'subtype')
+                type_maps.extension_action_subtype[wire_version][experimenter][cls] = val
+                if wire_version >= of_g.VERSION_1_3:
+                    cls2 = parent + "_id" + cls[len(parent):]
+                    type_maps.extension_action_id_subtype[wire_version][experimenter][cls2] = val
+
+    # Messages
+    for version, protocol in loxi_globals.ir.items():
+        wire_version = version.wire_version
+        for ofclass in protocol.classes:
+            cls = ofclass.name
+            # HACK (though this is what loxi_utils.class_is_message() does)
+            if not [x for x in ofclass.members if isinstance(x, OFDataMember) and x.name == 'xid']:
+                continue
+            if type_maps.class_is_virtual(cls):
+                continue
+            # HACK hide of_group subclasses from legacy c backend
+            if ofclass.name in ("of_group_add", "of_group_modify", "of_group_delete"):
+                continue
+            subcls = cls[3:]
+            val = find_type_value(ofclass, 'type')
+            if not val in type_maps.message_types[wire_version].values():
+                type_maps.message_types[wire_version][subcls] = val
+
+            # Extensions
+            experimenter = find_experimenter('of', cls)
+            if experimenter:
+                val = find_type_value(ofclass, 'subtype')
+                type_maps.extension_message_subtype[wire_version][experimenter][cls] = val
+
+    type_maps.generate_maps()
+
+def analyze_input():
+    """
+    Add information computed from the input, including offsets and
+    lengths of struct members and the set of list and action_id types.
+    """
+
+    # Generate header classes for inheritance parents
+    for wire_version, ordered_classes in of_g.ordered_classes.items():
+        classes = versions[of_g.of_version_wire2name[wire_version]]['classes']
+        for cls in ordered_classes:
+            if cls in type_maps.inheritance_map:
+                new_cls = cls + '_header'
+                of_g.ordered_classes[wire_version].append(new_cls)
+                classes[new_cls] = classes[cls]
+
+    # Generate action_id classes for OF 1.3
+    for wire_version, ordered_classes in of_g.ordered_classes.items():
+        if not wire_version in [of_g.VERSION_1_3]:
+            continue
+        classes = versions[of_g.of_version_wire2name[wire_version]]['classes']
+        for cls in ordered_classes:
+            if not loxi_utils.class_is_action(cls):
+                continue
+            action = cls[10:]
+            if action == '' or action == 'header':
+                continue
+            name = "of_action_id_" + action
+            members = classes["of_action"][:]
+            of_g.ordered_classes[wire_version].append(name)
+            if type_maps.action_id_is_extension(name, wire_version):
+                # Copy the base action classes thru subtype
+                members = classes["of_action_" + action][:4]
+            classes[name] = members
+
+    # @fixme If we support extended actions in OF 1.3, need to add IDs
+    # for them here
+
+    for wire_version in of_g.wire_ver_map.keys():
+        version_name = of_g.of_version_wire2name[wire_version]
+        calculate_offsets_and_lengths(
+            of_g.ordered_classes[wire_version],
+            versions[version_name]['classes'],
+            wire_version)
+
+def unify_input():
+    """
+    Create Unified View of Objects
+    """
+
+    global versions
+
+    # Add classes to unified in wire-format order so that it is easier
+    # to generate things later
+    keys = versions.keys()
+    keys.sort(reverse=True)
+    for version in keys:
+        wire_version = versions[version]["wire_version"]
+        classes = versions[version]["classes"]
+        for cls in of_g.ordered_classes[wire_version]:
+            add_class(wire_version, cls, classes[cls])
+
+
+def log_all_class_info():
+    """
+    Log the results of processing the input
+
+    Debug function
+    """
+
+    for cls in of_g.unified:
+        for v in of_g.unified[cls]:
+            if type(v) == type(0):
+                log("cls: %s. ver: %d. base len %d. %s" %
+                    (str(cls), v, of_g.base_length[(cls, v)],
+                     loxi_utils.class_is_var_len(cls,v) and "not fixed"
+                     or "fixed"))
+                if "use_version" in of_g.unified[cls][v]:
+                    log("cls %s: v %d mapped to %d" % (str(cls), v,
+                           of_g.unified[cls][v]["use_version"]))
+                if "members" in of_g.unified[cls][v]:
+                    for member in of_g.unified[cls][v]["members"]:
+                        log("   %-20s: type %-20s. offset %3d" %
+                            (member["name"], member["m_type"],
+                             member["offset"]))
+
+def generate_all_files():
+    """
+    Create the files for the language target
+    """
+    for (name, fn) in lang_module.targets.items():
+        path = of_g.options.install_dir + '/' + name
+        os.system("mkdir -p %s" % os.path.dirname(path))
+        with open(path, "w") as outfile:
+            fn(outfile, os.path.basename(name))
+        print("Wrote contents for " + name)
+
+if __name__ == '__main__':
+    of_g.loxigen_log_file = open("loxigen.log", "w")
+    of_g.loxigen_dbg_file = sys.stdout
+
+    of_g.process_commandline()
+    # @fixme Use command line params to select log
+
+    if not config_sanity_check():
+        debug("Config sanity check failed\n")
+        sys.exit(1)
+
+    # Import the language file
+    lang_file = "lang_%s" % of_g.options.lang
+    lang_module = __import__(lang_file)
+
+    # If list files, just list auto-gen files to stdout and exit
+    if of_g.options.list_files:
+        for name in lang_module.targets:
+            print of_g.options.install_dir + '/' + name
+        sys.exit(0)
+
+    log("\nGenerating files for target language %s\n" % of_g.options.lang)
+
+    initialize_versions()
+    read_input()
+    populate_type_maps()
+    analyze_input()
+    unify_input()
+    order_and_assign_object_ids()
+    log_all_class_info()
+    generate_all_files()
diff --git a/c_gen/c_code_gen.py b/c_gen/c_code_gen.py
index e35ee8d..1106f2d 100644
--- a/c_gen/c_code_gen.py
+++ b/c_gen/c_code_gen.py
@@ -31,7 +31,7 @@
 """
 
 import sys
-import of_g
+import c_gen.of_g_legacy as of_g
 import c_match
 from generic_utils import *
 from c_gen import flags, type_maps, c_type_maps
@@ -40,6 +40,7 @@
 
 import c_gen.identifiers as identifiers
 
+# 'property' is for queues. Could be trouble
 
 ################################################################
 #
diff --git a/c_gen/c_dump_gen.py b/c_gen/c_dump_gen.py
index 9d487e2..a0af14e 100644
--- a/c_gen/c_dump_gen.py
+++ b/c_gen/c_dump_gen.py
@@ -33,7 +33,7 @@
 """
 
 import sys
-import of_g
+import c_gen.of_g_legacy as of_g
 import c_gen.match as match
 import c_gen.flags as flags
 from generic_utils import *
diff --git a/c_gen/c_show_gen.py b/c_gen/c_show_gen.py
index fe3e3dc..fc3edb8 100644
--- a/c_gen/c_show_gen.py
+++ b/c_gen/c_show_gen.py
@@ -33,7 +33,7 @@
 """
 
 import sys
-import of_g
+import c_gen.of_g_legacy as of_g
 import c_gen.match as match
 import c_gen.flags as flags
 from generic_utils import *
diff --git a/c_gen/c_test_gen.py b/c_gen/c_test_gen.py
index c9c8245..bddc642 100644
--- a/c_gen/c_test_gen.py
+++ b/c_gen/c_test_gen.py
@@ -59,7 +59,7 @@
 """
 
 import sys
-import of_g
+import c_gen.of_g_legacy as of_g
 import c_gen.match as match
 import c_gen.flags as flags
 from generic_utils import *
diff --git a/c_gen/c_type_maps.py b/c_gen/c_type_maps.py
index 69dc804..acc2314 100644
--- a/c_gen/c_type_maps.py
+++ b/c_gen/c_type_maps.py
@@ -29,7 +29,7 @@
 # @brief C code generation for LOXI type related maps
 #
 
-import of_g
+import c_gen.of_g_legacy as of_g
 import sys
 from generic_utils import *
 import c_gen.type_maps as type_maps
diff --git a/c_gen/c_validator_gen.py b/c_gen/c_validator_gen.py
index e799918..126530a 100644
--- a/c_gen/c_validator_gen.py
+++ b/c_gen/c_validator_gen.py
@@ -33,7 +33,7 @@
 """
 
 import sys
-import of_g
+import c_gen.of_g_legacy as of_g
 import c_gen.match as match
 import c_gen.flags as flags
 from generic_utils import *
diff --git a/of_g.py b/c_gen/of_g_legacy.py
similarity index 85%
rename from of_g.py
rename to c_gen/of_g_legacy.py
index 7111259..adf5c2b 100644
--- a/of_g.py
+++ b/c_gen/of_g_legacy.py
@@ -32,7 +32,6 @@
 #
 
 import sys
-from optparse import OptionParser
 # @fixme Replace with argparse
 
 ################################################################
@@ -48,84 +47,9 @@
 wire_ver_map = {}
 
 ##
-# Command line options
-options = {}
-
-##
-# Command line arguments
-args = []
-
-##@var config_default
-# The default configuration dictionary for LOXI code generation
-options_default = {
-    "lang"               : "c",
-    "version-list"       : "1.0 1.1 1.2 1.3",
-    "install-dir"        : "loxi_output",
-}
-
-##
 # The list of wire versions which are to be supported
 target_version_list = []
 
-def lang_normalize(lang):
-    """
-    Normalize the representation of the language
-    """
-    return lang.lower()
-
-def version_list_normalize(vlist):
-    """
-    Normalize the version list and return as an array
-    """
-    out_list = []
-    # @fixme Map to OF version references
-    if vlist.find(',') > 0:
-        vlist = vlist.split(',')
-    else:
-        vlist = vlist.split()
-    vlist.sort()
-    for ver in vlist:
-        try:
-            out_list.append(of_param_version_map[ver])
-        except KeyError:
-            sys.stderr.write("Bad version input, %s" % str(ver))
-            sys.exit(1)
-
-    return out_list
-
-def process_commandline(default_vals=options_default):
-    """
-    Set up the options dictionary
-
-    @param cfg_dflt The default configuration dictionary
-    @return A pair (options, args) as per parser return
-    """
-    global options
-    global args
-    global target_version_list
-
-    parser = OptionParser(version="%prog 0.1")
-
-    #@todo Add options via dictionary
-    parser.add_option("--list-files", action="store_true", default=False,
-                      help="List output files generated")
-    parser.add_option("-l", "--lang", "--language",
-                      default=default_vals["lang"],
-                      help="Select the target language: c, python")
-    parser.add_option("-i", "--install-dir",
-                      default=default_vals["install-dir"],
-                      help="Directory to install generated files to (default %s)" % default_vals["install-dir"])
-    parser.add_option("-v", "--version-list",
-                      default=default_vals["version-list"],
-                      help="Specify the versions to target as 1.0 1.1 etc")
-
-    (options, args) = parser.parse_args()
-
-    options.lang = lang_normalize(options.lang)
-    target_version_list = version_list_normalize(options.version_list)
-    target_version_list.sort()
-    return (options, args)
-
 ##
 # The dictionary of config variables related to code
 #
@@ -352,12 +276,6 @@
     uint64_t metadata[(OF_OBJECT_METADATA_BYTES + 7) / 8];
 """
 
-# LOXI intermediate representation
-# This is a hash from wire versions to OFProtocol objects.
-# See loxi_ir.py
-
-ir = {}
-
 ##
 # LOXI identifiers
 #
diff --git a/c_gen/templates/locitest/test_validator.c b/c_gen/templates/locitest/test_validator.c
index eb3cbb6..a4b55eb 100644
--- a/c_gen/templates/locitest/test_validator.c
+++ b/c_gen/templates/locitest/test_validator.c
@@ -26,7 +26,7 @@
 :: # under the EPL.
 ::
 :: include('_copyright.c')
-:: import of_g
+:: import c_gen.of_g_legacy as of_g
 :: import c_gen.loxi_utils_legacy as loxi_utils
 :: from c_gen import type_maps
 
diff --git a/cmdline.py b/cmdline.py
new file mode 100644
index 0000000..aafa019
--- /dev/null
+++ b/cmdline.py
@@ -0,0 +1,108 @@
+# 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 optparse import OptionParser
+
+from loxi_globals import OFVersions
+
+##
+# Command line options
+options = {}
+
+##
+# Command line arguments
+args = []
+
+##@var config_default
+# The default configuration dictionary for LOXI code generation
+options_default = {
+    "lang"               : "c",
+    "version-list"       : "1.0 1.1 1.2 1.3",
+    "install-dir"        : "loxi_output",
+}
+
+def lang_normalize(lang):
+    """
+    Normalize the representation of the language
+    """
+    return lang.lower()
+
+def version_list_normalize(vlist):
+    """
+    Normalize the version list and return as an array
+    """
+    out_list = []
+    # @fixme Map to OF version references
+    if vlist.find(',') > 0:
+        vlist = vlist.split(',')
+    else:
+        vlist = vlist.split()
+    vlist.sort()
+    for ver in vlist:
+        try:
+            out_list.append(OFVersions.from_string(ver))
+        except KeyError:
+            sys.stderr.write("Bad version input, %s" % str(ver))
+            sys.exit(1)
+    return out_list
+
+def process_commandline(default_vals=options_default):
+    """
+    Set up the options dictionary
+
+    @param cfg_dflt The default configuration dictionary
+    @return A pair (options, args) as per parser return
+    """
+    global options
+    global args
+    global target_version_list
+
+    parser = OptionParser(version="%prog 0.1")
+
+    #@todo Add options via dictionary
+    parser.add_option("--list-files", action="store_true", default=False,
+                      help="List output files generated")
+    parser.add_option("-l", "--lang", "--language",
+                      default=default_vals["lang"],
+                      help="Select the target language: c, python")
+    parser.add_option("-i", "--install-dir",
+                      default=default_vals["install-dir"],
+                      help="Directory to install generated files to (default %s)" % default_vals["install-dir"])
+    parser.add_option("-v", "--verbose",
+                      action="store_true", default=False,
+                      help="Debug output")
+
+    parser.add_option("-V", "--version-list",
+                      default=default_vals["version-list"],
+                      help="Specify the versions to target as 1.0 1.1 etc")
+
+    (options, args) = parser.parse_args()
+
+    options.lang = lang_normalize(options.lang)
+    target_version_list = version_list_normalize(options.version_list)
+    target_version_list.sort()
+    return (options, args, target_version_list)
diff --git a/generic_utils.py b/generic_utils.py
index ea9c589..3ed67ce 100644
--- a/generic_utils.py
+++ b/generic_utils.py
@@ -30,30 +30,10 @@
 
 Intended to be imported into another namespace
 """
-
+import logging
 import collections
 import functools
 import sys
-import of_g
-
-
-################################################################
-#
-# Configuration related
-#
-################################################################
-
-def config_check(str, dictionary = of_g.code_gen_config):
-    """
-    Return config value if in dictionary; else return False.
-    @param str The lookup index
-    @param dictionary The dict to check; use code_gen_config if None
-    """
-
-    if str in dictionary:
-        return dictionary[str]
-
-    return False
 
 ################################################################
 #
@@ -63,18 +43,17 @@
 
 def debug(obj):
     """
-    Debug output to the current both the log file and debug output
-    @param out_str The stringified output to write
+    Legacy logging method. Delegate to logging.debug.
+    Use logging.debug directly in the future.S
     """
-    of_g.loxigen_dbg_file.write(str(obj) + "\n")
-    log(obj)
+    logging.debug(obj)
 
 def log(obj):
     """
-    Log output to the current global log file
-    @param out_str The stringified output to write
+    Legacy logging method. Delegate to logging.info.
+    Use logging.info directly in the future.S
     """
-    of_g.loxigen_log_file.write(str(obj) + "\n")
+    logging.info(obj)
 
 ################################################################
 #
diff --git a/java_gen/codegen.py b/java_gen/codegen.py
index f349f9e..1ba0b8f 100644
--- a/java_gen/codegen.py
+++ b/java_gen/codegen.py
@@ -29,11 +29,12 @@
 @brief Main Java Generation module
 """
 
+import logging
 import pdb
 import os
 import shutil
 
-import of_g
+import loxi_globals
 from loxi_ir import *
 import lang_java
 import test_data
@@ -44,9 +45,11 @@
 
 import java_gen.java_model as java_model
 
-def gen_all_java():
-    basedir= '%s/openflowj' % of_g.options.install_dir
-    print "Outputting to %s" % basedir
+logger = logging.getLogger(__name__)
+
+def gen_all_java(install_dir):
+    basedir= '%s/openflowj' % install_dir
+    logger.info("Outputting to %s" % basedir)
     if os.path.exists(basedir):
         shutil.rmtree(basedir)
     os.makedirs(basedir)
diff --git a/lang_c.py b/lang_c.py
index 9cfc2fa..762f82c 100644
--- a/lang_c.py
+++ b/lang_c.py
@@ -33,6 +33,8 @@
 """
 
 import os
+import c_gen.of_g_legacy as of_g
+import c_gen.build_of_g as build_of_g
 import c_gen.c_code_gen as c_code_gen
 import c_gen.c_test_gen as c_test_gen
 import c_gen.c_dump_gen as c_dump_gen
@@ -40,6 +42,7 @@
 import c_gen.c_validator_gen as c_validator_gen
 import c_gen.util
 import loxi_utils.loxi_utils as loxi_utils
+import template_utils
 
 def static(out, name):
     c_gen.util.render_template(out, os.path.basename(name))
@@ -112,7 +115,61 @@
     'locitest/Makefile': static,
 }
 
-def generate():
+################################################################
+#
+# Configuration related
+#
+################################################################
+
+def config_check(str, dictionary = of_g.code_gen_config):
+    """
+    Return config value if in dictionary; else return False.
+    @param str The lookup index
+    @param dictionary The dict to check; use code_gen_config if None
+    """
+
+    if str in dictionary:
+        return dictionary[str]
+
+    return False
+
+def config_sanity_check():
+    """
+    Check the configuration for basic consistency
+
+    @fixme Needs update for generic language support
+    """
+
+    rv = True
+    # For now, only "error" supported for get returns
+    if config_check("copy_semantics") != "read":
+        debug("Only 'read' is supported for copy_semantics");
+        rv = False
+    if config_check("get_returns") != "error":
+        debug("Only 'error' is supported for get-accessor return types\m");
+        rv = False
+    if not config_check("use_fn_ptrs") and not config_check("gen_unified_fns"):
+        debug("Must have gen_fn_ptrs and/or gen_unified_fns set in config")
+        rv = False
+    if config_check("use_obj_id"):
+        debug("use_obj_id is set but not yet supported (change \
+config_sanity_check if it is)")
+        rv = False
+    if config_check("gen_unified_macros") and config_check("gen_unified_fns") \
+            and config_check("gen_unified_macro_lower"):
+        debug("Conflict: Cannot generate unified functions and lower case \
+unified macros")
+        rv = False
+
+    return rv
+
+def generate(install_dir):
+    build_of_g.initialize_versions()
+    build_of_g.build_ordered_classes()
+    build_of_g.populate_type_maps()
+    build_of_g.analyze_input()
+    build_of_g.unify_input()
+    build_of_g.order_and_assign_object_ids()
     for (name, fn) in targets.items():
-        with loxi_utils.open_output(name) as outfile:
+        with template_utils.open_output(install_dir, name) as outfile:
             fn(outfile, os.path.basename(name))
diff --git a/loxi_globals.py b/loxi_globals.py
new file mode 100644
index 0000000..93b24b5
--- /dev/null
+++ b/loxi_globals.py
@@ -0,0 +1,72 @@
+# 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 loxi_ir import *
+
+#######################################################################
+### OFVersion registry
+#######################################################################
+
+class OFVersions:
+    VERSION_1_0 = OFVersion("1.0", 1)
+    VERSION_1_1 = OFVersion("1.1", 2)
+    VERSION_1_2 = OFVersion("1.2", 3)
+    VERSION_1_3 = OFVersion("1.3", 4)
+
+    all_supported = (
+        VERSION_1_0,
+        VERSION_1_1,
+        VERSION_1_2,
+        VERSION_1_3,
+    )
+
+    wire_version_map   = { v.wire_version : v for v in all_supported }
+    version_string_map = { v.version      : v for v in all_supported }
+
+    target_versions = []
+
+    @staticmethod
+    def from_wire(w):
+        return OFVersions.wire_version_map[w]
+
+    @staticmethod
+    def from_string(s):
+        return OFVersions.version_string_map[s]
+
+    @staticmethod
+    def from_strings(*strings):
+        return tuple(OFVersions.version_string_map[s] for s in strings)
+
+
+
+
+#######################################################################
+### OFVersions
+#######################################################################
+
+# map OFVersion -> OFProtocol
+ir = {}
diff --git a/loxigen.py b/loxigen.py
index 7b78f63..ba62288 100755
--- a/loxigen.py
+++ b/loxigen.py
@@ -69,248 +69,27 @@
 
 """
 
-import sys
-
+from collections import OrderedDict, defaultdict
+import copy
+import glob
+from optparse import OptionParser
+import os
 import re
 import string
-import os
-import glob
-import copy
-import collections
-import of_g
-import loxi_front_end.type_maps as type_maps
+import sys
+
+import cmdline
+from loxi_globals import OFVersions
+import loxi_globals
 import loxi_utils.loxi_utils as loxi_utils
-import loxi_front_end.c_parse_utils as c_parse_utils
-import loxi_front_end.identifiers as identifiers
 import pyparsing
 import loxi_front_end.parser as parser
-import loxi_front_end.translation as translation
 import loxi_front_end.frontend as frontend
-from loxi_ir import *
+import loxi_ir
 from generic_utils import *
 
 root_dir = os.path.dirname(os.path.realpath(__file__))
 
-# TODO:  Put these in a class so they get documented
-
-## Dict indexed by version giving all info related to version
-#
-# This is local; after processing, the information is stored in
-# of_g variables.
-versions = {}
-
-def config_sanity_check():
-    """
-    Check the configuration for basic consistency
-
-    @fixme Needs update for generic language support
-    """
-
-    rv = True
-    # For now, only "error" supported for get returns
-    if config_check("copy_semantics") != "read":
-        debug("Only 'read' is supported for copy_semantics");
-        rv = False
-    if config_check("get_returns") != "error":
-        debug("Only 'error' is supported for get-accessor return types\m");
-        rv = False
-    if not config_check("use_fn_ptrs") and not config_check("gen_unified_fns"):
-        debug("Must have gen_fn_ptrs and/or gen_unified_fns set in config")
-        rv = False
-    if config_check("use_obj_id"):
-        debug("use_obj_id is set but not yet supported (change \
-config_sanity_check if it is)")
-        rv = False
-    if config_check("gen_unified_macros") and config_check("gen_unified_fns") \
-            and config_check("gen_unified_macro_lower"):
-        debug("Conflict: Cannot generate unified functions and lower case \
-unified macros")
-        rv = False
-
-    return rv
-
-def add_class(wire_version, cls, members):
-    """
-    Process a class for the given version and update the unified
-    list of classes as needed.
-
-    @param wire_version The wire version for this class defn
-    @param cls The name of the class being added
-    @param members The list of members with offsets calculated
-    """
-    memid = 0
-
-    sig = loxi_utils.class_signature(members)
-    if cls in of_g.unified:
-        uc = of_g.unified[cls]
-        if wire_version in uc:
-            debug("Error adding %s to unified. Wire ver %d exists" %
-                  (cls, wire_version))
-            sys.exit(1)
-        uc[wire_version] = {}
-        # Check for a matching signature
-        for wver in uc:
-            if type(wver) != type(0): continue
-            if wver == wire_version: continue
-            if not "use_version" in uc[wver]:
-                if sig == loxi_utils.class_signature(uc[wver]["members"]):
-                    log("Matched %s, ver %d to ver %d" %
-                          (cls, wire_version, wver))
-                    # have a match with existing version
-                    uc[wire_version]["use_version"] = wver
-                    # What else to do?
-                    return
-    else:  # Haven't seen this entry before
-        log("Adding %s to unified list, ver %d" % (cls, wire_version))
-        of_g.unified[cls] = dict(union={})
-        uc = of_g.unified[cls]
-
-    # At this point, need to add members for this version
-    uc[wire_version] = dict(members = members)
-
-    # Per member processing:
-    #  Add to union list (I'm sure there's a better way)
-    #  Check if it's a list
-    union = uc["union"]
-    if not cls in of_g.ordered_members:
-        of_g.ordered_members[cls] = []
-    for member in members:
-        m_name = member["name"]
-        m_type = member["m_type"]
-        if m_name.find("pad") == 0:
-            continue
-        if m_name in union:
-            if not m_type == union[m_name]["m_type"]:
-                debug("ERROR:   CLASS: %s. VERSION %d. MEMBER: %s. TYPE: %s" %
-                      (cls, wire_version, m_name, m_type))
-                debug("    Type conflict adding member to unified set.")
-                debug("    Current union[%s]:" % m_name)
-                debug(union[m_name])
-                sys.exit(1)
-        else:
-            union[m_name] = dict(m_type=m_type, memid=memid)
-            memid += 1
-        if not m_name in of_g.ordered_members[cls]:
-            of_g.ordered_members[cls].append(m_name)
-
-def update_offset(cls, wire_version, name, offset, m_type):
-    """
-    Update (and return) the offset based on type.
-    @param cls The parent class
-    @param wire_version The wire version being processed
-    @param name The name of the data member
-    @param offset The current offset
-    @param m_type The type declaration being processed
-    @returns A pair (next_offset, len_update)  next_offset is the new offset
-    of the next object or -1 if this is a var-length object.  len_update
-    is the increment that should be added to the length.  Note that (for
-    of_match_v3) it is variable length, but it adds 8 bytes to the fixed
-    length of the object
-    If offset is already -1, do not update
-    Otherwise map to base type and count and update (if possible)
-    """
-    if offset < 0:    # Don't update offset once set to -1
-        return offset, 0
-
-    count, base_type = c_parse_utils.type_dec_to_count_base(m_type)
-
-    len_update = 0
-    if base_type in of_g.of_mixed_types:
-        base_type = of_g.of_mixed_types[base_type][wire_version]
-
-    base_class = base_type[:-2]
-    if (base_class, wire_version) in of_g.is_fixed_length:
-        bytes = of_g.base_length[(base_class, wire_version)]
-    else:
-        if base_type == "of_match_v3_t":
-            # This is a special case: it has non-zero min length
-            # but is variable length
-            bytes = -1
-            len_update = 8
-        elif base_type in of_g.of_base_types:
-            bytes = of_g.of_base_types[base_type]["bytes"]
-        else:
-            print "UNKNOWN TYPE for %s %s: %s" % (cls, name, base_type)
-            log("UNKNOWN TYPE for %s %s: %s" % (cls, name, base_type))
-            bytes = -1
-
-    # If bytes
-    if bytes > 0:
-        len_update = count * bytes
-
-    if bytes == -1:
-        return -1, len_update
-
-    return offset + (count * bytes), len_update
-
-def calculate_offsets_and_lengths(ordered_classes, classes, wire_version):
-    """
-    Generate the offsets for fixed offset class members
-    Also calculate the class_sizes when possible.
-
-    @param classes The classes to process
-    @param wire_version The wire version for this set of classes
-
-    Updates global variables
-    """
-
-    lists = set()
-
-    # Generate offsets
-    for cls in ordered_classes:
-        fixed_offset = 0 # The last "good" offset seen
-        offset = 0
-        last_offset = 0
-        last_name = "-"
-        for member in classes[cls]:
-            m_type = member["m_type"]
-            name = member["name"]
-            if last_offset == -1:
-                if name == "pad":
-                    log("Skipping pad for special offset for %s" % cls)
-                else:
-                    log("SPECIAL OFS: Member %s (prev %s), class %s ver %d" %
-                          (name, last_name, cls, wire_version))
-                    if (((cls, name) in of_g.special_offsets) and
-                        (of_g.special_offsets[(cls, name)] != last_name)):
-                        debug("ERROR: special offset prev name changed")
-                        debug("  cls %s. name %s. version %d. was %s. now %s" %
-                              cls, name, wire_version,
-                              of_g.special_offsets[(cls, name)], last_name)
-                        sys.exit(1)
-                    of_g.special_offsets[(cls, name)] = last_name
-
-            member["offset"] = offset
-            if m_type.find("list(") == 0:
-                (list_name, base_type) = loxi_utils.list_name_extract(m_type)
-                lists.add(list_name)
-                member["m_type"] = list_name + "_t"
-                offset = -1
-            elif m_type.find("struct") == 0:
-                debug("ERROR found struct: %s.%s " % (cls, name))
-                sys.exit(1)
-            elif m_type == "octets":
-                log("offset gen skipping octets: %s.%s " % (cls, name))
-                offset = -1
-            else:
-                offset, len_update = update_offset(cls, wire_version, name,
-                                                  offset, m_type)
-                if offset != -1:
-                    fixed_offset = offset
-                else:
-                    fixed_offset += len_update
-                    log("offset is -1 for %s.%s version %d " %
-                        (cls, name, wire_version))
-            last_offset = offset
-            last_name = name
-        of_g.base_length[(cls, wire_version)] = fixed_offset
-        if (offset != -1):
-            of_g.is_fixed_length.add((cls, wire_version))
-    for list_type in lists:
-        classes[list_type] = []
-        of_g.ordered_classes[wire_version].append(list_type)
-        of_g.base_length[(list_type, wire_version)] = 0
-
 def process_input_file(filename):
     """
     Process an input file
@@ -332,78 +111,13 @@
 
     # Create the OFInput from the AST
     try:
-        ofinput = frontend.create_ofinput(ast)
+        ofinput = frontend.create_ofinput(filename, ast)
     except frontend.InputError as e:
         print "Error in %s: %s" % (os.path.basename(filename), str(e))
         sys.exit(1)
 
     return ofinput
 
-def order_and_assign_object_ids():
-    """
-    Order all classes and assign object ids to all classes.
-
-    This is done to promote a reasonable order of the objects, putting
-    messages first followed by non-messages.  No assumptions should be
-    made about the order, nor about contiguous numbering.  However, the
-    numbers should all be reasonably small allowing arrays indexed by
-    these enum values to be defined.
-    """
-
-    # Generate separate message and non-message ordered lists
-    for cls in of_g.unified:
-        if loxi_utils.class_is_message(cls):
-            of_g.ordered_messages.append(cls)
-        elif loxi_utils.class_is_list(cls):
-            of_g.ordered_list_objects.append(cls)
-        else:
-            of_g.ordered_non_messages.append(cls)
-
-    of_g.ordered_messages.sort()
-    of_g.ordered_pseudo_objects.sort()
-    of_g.ordered_non_messages.sort()
-    of_g.ordered_list_objects.sort()
-    of_g.standard_class_order.extend(of_g.ordered_messages)
-    of_g.standard_class_order.extend(of_g.ordered_non_messages)
-    of_g.standard_class_order.extend(of_g.ordered_list_objects)
-
-    # This includes pseudo classes for which most code is not generated
-    of_g.all_class_order.extend(of_g.ordered_messages)
-    of_g.all_class_order.extend(of_g.ordered_non_messages)
-    of_g.all_class_order.extend(of_g.ordered_list_objects)
-    of_g.all_class_order.extend(of_g.ordered_pseudo_objects)
-
-    # Assign object IDs
-    for cls in of_g.ordered_messages:
-        of_g.unified[cls]["object_id"] = of_g.object_id
-        of_g.object_id += 1
-    for cls in of_g.ordered_non_messages:
-        of_g.unified[cls]["object_id"] = of_g.object_id
-        of_g.object_id += 1
-    for cls in of_g.ordered_list_objects:
-        of_g.unified[cls]["object_id"] = of_g.object_id
-        of_g.object_id += 1
-    for cls in of_g.ordered_pseudo_objects:
-        of_g.unified[cls] = {}
-        of_g.unified[cls]["object_id"] = of_g.object_id
-        of_g.object_id += 1
-
-
-def initialize_versions():
-    """
-    Create an empty datastructure for each target version.
-    """
-
-    for wire_version in of_g.target_version_list:
-        version_name = of_g.of_version_wire2name[wire_version]
-        of_g.wire_ver_map[wire_version] = version_name
-        versions[version_name] = dict(
-            version_name = version_name,
-            wire_version = wire_version,
-            classes = {})
-        of_g.ordered_classes[wire_version] = []
-
-
 def read_input():
     """
     Read in from files given on command line and update global state
@@ -411,7 +125,7 @@
     @fixme Should select versions to support from command line
     """
 
-    ofinputs_by_version = collections.defaultdict(lambda: [])
+    ofinputs_by_version = defaultdict(lambda: [])
     filenames = sorted(glob.glob("%s/openflow_input/*" % root_dir))
 
     # Ignore emacs backup files
@@ -421,245 +135,50 @@
         log("Processing struct file: " + filename)
         ofinput = process_input_file(filename)
 
-        # Populate global state
         for wire_version in ofinput.wire_versions:
             ofinputs_by_version[wire_version].append(ofinput)
-            version_name = of_g.of_version_wire2name[wire_version]
+    return ofinputs_by_version
 
-            for ofclass in ofinput.classes:
-                of_g.ordered_classes[wire_version].append(ofclass.name)
-                legacy_members = []
-                pad_count = 0
-                for m in ofclass.members:
-                    if type(m) == OFPadMember:
-                        m_name = 'pad%d' % pad_count
-                        if m_name == 'pad0': m_name = 'pad'
-                        legacy_members.append(dict(m_type='uint8_t[%d]' % m.length,
-                                                   name=m_name))
-                        pad_count += 1
-                    else:
-                        # HACK the C backend does not yet support of_oxm_t
-                        if m.oftype == 'of_oxm_t':
-                            m_type = 'of_octets_t'
-                        else:
-                            enum = find(lambda e: e.name == m.oftype, ofinput.enums)
-                            if enum and "wire_type" in enum.params:
-                                m_type = enum.params["wire_type"]
-                            else:
-                                m_type = m.oftype
-                        legacy_members.append(dict(m_type=m_type, name=m.name))
-                versions[version_name]['classes'][ofclass.name] = legacy_members
+def build_ir(ofinputs_by_version):
+    classes = []
+    enums = []
+    for wire_version, ofinputs in ofinputs_by_version.items():
+        version = OFVersions.from_wire(wire_version)
+        ofprotocol = loxi_ir.build_protocol(version, ofinputs)
+        loxi_globals.ir[version] = ofprotocol
 
-            for enum in ofinput.enums:
-                for entry in enum.entries:
-                    identifiers.add_identifier(
-                        translation.loxi_name(entry.name),
-                        entry.name, enum.name, entry.value, wire_version,
-                        of_g.identifiers, of_g.identifiers_by_group)
+    loxi_globals.unified = loxi_ir.build_unified_ir(loxi_globals.ir)
 
-        for wire_version, ofinputs in ofinputs_by_version.items():
-            ofprotocol = OFProtocol(wire_version=wire_version, classes=[], enums=[])
-            for ofinput in ofinputs:
-                ofprotocol.classes.extend(ofinput.classes)
-                ofprotocol.enums.extend(ofinput.enums)
-            ofprotocol.classes.sort(key=lambda ofclass: ofclass.name)
-            of_g.ir[wire_version] = ofprotocol
-
-def populate_type_maps():
-    """
-    Use the type members in the IR to fill out the legacy type_maps.
-    """
-
-    def split_inherited_cls(cls):
-        if cls == 'of_meter_band_stats': # HACK not a subtype of of_meter_band
-            return None, None
-        for parent in sorted(type_maps.inheritance_data.keys(), reverse=True):
-            if cls.startswith(parent):
-                return (parent, cls[len(parent)+1:])
-        return None, None
-
-    def find_experimenter(parent, cls):
-        for experimenter in sorted(of_g.experimenter_name_to_id.keys(), reverse=True):
-            prefix = parent + '_' + experimenter
-            if cls.startswith(prefix) and cls != prefix:
-                return experimenter
-        return None
-
-    def find_type_value(ofclass, m_name):
-        for m in ofclass.members:
-            if isinstance(m, OFTypeMember) and m.name == m_name:
-                return m.value
-        raise KeyError("ver=%d, cls=%s, m_name=%s" % (wire_version, cls, m_name))
-
-    # Most inheritance classes: actions, instructions, etc
-    for wire_version, protocol in of_g.ir.items():
-        for ofclass in protocol.classes:
-            cls = ofclass.name
-            parent, subcls = split_inherited_cls(cls)
-            if not (parent and subcls):
-                continue
-            if parent == 'of_oxm':
-                type_len = find_type_value(ofclass, 'type_len')
-                oxm_class = (type_len >> 16) & 0xffff
-                if oxm_class != 0x8000:
-                    # Do not include experimenter OXMs in the main table
-                    val = type_maps.invalid_type
-                else:
-                    val = (type_len >> 8) & 0xff
-            else:
-                val = find_type_value(ofclass, 'type')
-            type_maps.inheritance_data[parent][wire_version][subcls] = val
-
-            # Extensions (only actions for now)
-            experimenter = find_experimenter(parent, cls)
-            if parent == 'of_action' and experimenter:
-                val = find_type_value(ofclass, 'subtype')
-                type_maps.extension_action_subtype[wire_version][experimenter][cls] = val
-                if wire_version >= of_g.VERSION_1_3:
-                    cls2 = parent + "_id" + cls[len(parent):]
-                    type_maps.extension_action_id_subtype[wire_version][experimenter][cls2] = val
-
-    # Messages
-    for wire_version, protocol in of_g.ir.items():
-        for ofclass in protocol.classes:
-            cls = ofclass.name
-            # HACK (though this is what loxi_utils.class_is_message() does)
-            if not [x for x in ofclass.members if isinstance(x, OFDataMember) and x.name == 'xid']:
-                continue
-            if type_maps.class_is_virtual(cls):
-                continue
-            subcls = cls[3:]
-            val = find_type_value(ofclass, 'type')
-            if not val in type_maps.message_types[wire_version].values():
-                type_maps.message_types[wire_version][subcls] = val
-
-            # Extensions
-            experimenter = find_experimenter('of', cls)
-            if experimenter:
-                val = find_type_value(ofclass, 'subtype')
-                type_maps.extension_message_subtype[wire_version][experimenter][cls] = val
-
-    type_maps.generate_maps()
-
-def analyze_input():
-    """
-    Add information computed from the input, including offsets and
-    lengths of struct members and the set of list and action_id types.
-    """
-
-    # Generate header classes for inheritance parents
-    for wire_version, ordered_classes in of_g.ordered_classes.items():
-        classes = versions[of_g.of_version_wire2name[wire_version]]['classes']
-        for cls in ordered_classes:
-            if cls in type_maps.inheritance_map:
-                new_cls = cls + '_header'
-                of_g.ordered_classes[wire_version].append(new_cls)
-                classes[new_cls] = classes[cls]
-
-    # Generate action_id classes for OF 1.3
-    for wire_version, ordered_classes in of_g.ordered_classes.items():
-        if not wire_version in [of_g.VERSION_1_3]:
-            continue
-        classes = versions[of_g.of_version_wire2name[wire_version]]['classes']
-        for cls in ordered_classes:
-            if not loxi_utils.class_is_action(cls):
-                continue
-            action = cls[10:]
-            if action == '' or action == 'header':
-                continue
-            name = "of_action_id_" + action
-            members = classes["of_action"][:]
-            of_g.ordered_classes[wire_version].append(name)
-            if type_maps.action_id_is_extension(name, wire_version):
-                # Copy the base action classes thru subtype
-                members = classes["of_action_" + action][:4]
-            classes[name] = members
-
-    # @fixme If we support extended actions in OF 1.3, need to add IDs
-    # for them here
-
-    for wire_version in of_g.wire_ver_map.keys():
-        version_name = of_g.of_version_wire2name[wire_version]
-        calculate_offsets_and_lengths(
-            of_g.ordered_classes[wire_version],
-            versions[version_name]['classes'],
-            wire_version)
-
-def unify_input():
-    """
-    Create Unified View of Objects
-    """
-
-    global versions
-
-    # Add classes to unified in wire-format order so that it is easier
-    # to generate things later
-    keys = versions.keys()
-    keys.sort(reverse=True)
-    for version in keys:
-        wire_version = versions[version]["wire_version"]
-        classes = versions[version]["classes"]
-        for cls in of_g.ordered_classes[wire_version]:
-            add_class(wire_version, cls, classes[cls])
-
-
-def log_all_class_info():
-    """
-    Log the results of processing the input
-
-    Debug function
-    """
-
-    for cls in of_g.unified:
-        for v in of_g.unified[cls]:
-            if type(v) == type(0):
-                log("cls: %s. ver: %d. base len %d. %s" %
-                    (str(cls), v, of_g.base_length[(cls, v)],
-                     loxi_utils.class_is_var_len(cls,v) and "not fixed"
-                     or "fixed"))
-                if "use_version" in of_g.unified[cls][v]:
-                    log("cls %s: v %d mapped to %d" % (str(cls), v,
-                           of_g.unified[cls][v]["use_version"]))
-                if "members" in of_g.unified[cls][v]:
-                    for member in of_g.unified[cls][v]["members"]:
-                        log("   %-20s: type %-20s. offset %3d" %
-                            (member["name"], member["m_type"],
-                             member["offset"]))
-
-def generate_all_files():
-    """
-    Create the files for the language target
-    """
-    lang_module.generate()
+################################################################
+#
+# Debug
+#
+################################################################
 
 if __name__ == '__main__':
-    of_g.loxigen_log_file = open("loxigen.log", "w")
-    of_g.loxigen_dbg_file = sys.stdout
-
-    of_g.process_commandline()
+    (options, args, target_versions) = cmdline.process_commandline()
     # @fixme Use command line params to select log
 
-    if not config_sanity_check():
+    logging.basicConfig(level = logging.INFO if not options.verbose else logging.DEBUG)
+
+    # Import the language file
+    lang_file = "lang_%s" % options.lang
+    lang_module = __import__(lang_file)
+
+    if hasattr(lang_module, "config_sanity_check") and not lang_module.config_sanity_check():
         debug("Config sanity check failed\n")
         sys.exit(1)
 
-    # Import the language file
-    lang_file = "lang_%s" % of_g.options.lang
-    lang_module = __import__(lang_file)
-
     # If list files, just list auto-gen files to stdout and exit
-    if of_g.options.list_files:
+    if options.list_files:
         for name in lang_module.targets:
-            print of_g.options.install_dir + '/' + name
+            print options.install_dir + '/' + name
         sys.exit(0)
 
-    log("\nGenerating files for target language %s\n" % of_g.options.lang)
+    log("\nGenerating files for target language %s\n" % options.lang)
 
-    initialize_versions()
-    read_input()
-    populate_type_maps()
-    analyze_input()
-    unify_input()
-    order_and_assign_object_ids()
-    log_all_class_info()
-    generate_all_files()
+    loxi_globals.OFVersions.target_versions = target_versions
+    inputs = read_input()
+    build_ir(inputs)
+    #log_all_class_info()
+    lang_module.generate(options.install_dir)