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)