loxigen: allow structs to refer to enums in other input files

Do a first pass over the input files to extract the enums.

Also hoists the OFProtocol construction from the loop over inputs. It only
needs to be done once.
diff --git a/loxi_front_end/frontend.py b/loxi_front_end/frontend.py
index 6746584..35c1e44 100644
--- a/loxi_front_end/frontend.py
+++ b/loxi_front_end/frontend.py
@@ -115,8 +115,4 @@
     if not ofinput.wire_versions:
         raise InputError("Missing #version metadata")
 
-    for used_enum in ctx.used_enums:
-        if not find(lambda e: e.name == used_enum, ofinput.enums):
-            raise Exception("Undeclared enum used in OFInput: {}".format(used_enum))
-
     return ofinput
diff --git a/loxigen.py b/loxigen.py
index 31214ba..ac5d536 100755
--- a/loxigen.py
+++ b/loxigen.py
@@ -417,13 +417,35 @@
     # Ignore emacs backup files
     filenames = [x for x in filenames if not x.endswith('~')]
 
+    # Read input files
+    all_ofinputs = []
     for filename in filenames:
         log("Processing struct file: " + filename)
         ofinput = process_input_file(filename)
-
-        # Populate global state
+        all_ofinputs.append(ofinput)
         for wire_version in ofinput.wire_versions:
             ofinputs_by_version[wire_version].append(ofinput)
+
+    # Merge input files into per-version 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
+
+    # Extract enums
+    # An input file can refer to an enum in another file
+    enums_by_version = { ver: {} for ver in ofinputs_by_version }
+    for ofinput in all_ofinputs:
+        for wire_version in ofinput.wire_versions:
+            for enum in ofinput.enums:
+                enums_by_version[wire_version][enum.name] = enum
+
+    # Populate legacy maps
+    for ofinput in all_ofinputs:
+        for wire_version in ofinput.wire_versions:
             version_name = of_g.of_version_wire2name[wire_version]
 
             for ofclass in ofinput.classes:
@@ -442,7 +464,7 @@
                         if m.oftype == 'of_oxm_t':
                             m_type = 'of_octets_t'
                         else:
-                            enum = find(lambda e: e.name == m.oftype, ofinput.enums)
+                            enum = enums_by_version[wire_version].get(m.oftype)
                             if enum and "wire_type" in enum.params:
                                 m_type = enum.params["wire_type"]
                             else:
@@ -457,14 +479,6 @@
                         entry.name, enum.name, entry.value, wire_version,
                         of_g.identifiers, of_g.identifiers_by_group)
 
-        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.