On 22 Nov 2021, at 12:22, Adrian Moreno wrote:

> In order to be able to reuse the core extaction logic, split the command

extaction -> extraction

> in two parts. The core extraction logic is moved to python/build while
> the command that writes the different files out of the extracted field
> info is kept in build-aux

Add dot

>
> Signed-off-by: Adrian Moreno <[email protected]>
> ---
>  build-aux/extract-ofp-fields       | 393 +----------------------------
>  python/automake.mk                 |   3 +-
>  python/build/extract_ofp_fields.py | 386 ++++++++++++++++++++++++++++
>  3 files changed, 397 insertions(+), 385 deletions(-)
>  create mode 100644 python/build/extract_ofp_fields.py
>
> diff --git a/build-aux/extract-ofp-fields b/build-aux/extract-ofp-fields
> index 8766995d9..725718336 100755
> --- a/build-aux/extract-ofp-fields
> +++ b/build-aux/extract-ofp-fields
> @@ -7,78 +7,16 @@ import re

The re module is no longer used, so you can remove the import.

>  import xml.dom.minidom
>  import build.nroff
>
> -line = ""
> -
> -# Maps from user-friendly version number to its protocol encoding.
> -VERSION = {"1.0": 0x01,
> -           "1.1": 0x02,
> -           "1.2": 0x03,
> -           "1.3": 0x04,
> -           "1.4": 0x05,
> -           "1.5": 0x06}
> -VERSION_REVERSE = dict((v,k) for k, v in VERSION.items())
> +from build.extract_ofp_fields import (
> +    extract_ofp_fields,
> +    PREREQS,
> +    OXM_CLASSES,
> +    VERSION,
> +    fatal,
> +    n_errors
> +)
>
> -TYPES = {"u8":       (1,   False),
> -         "be16":     (2,   False),
> -         "be32":     (4,   False),
> -         "MAC":      (6,   False),
> -         "be64":     (8,   False),
> -         "be128":    (16,  False),
> -         "tunnelMD": (124, True)}
> -
> -FORMATTING = {"decimal":            ("MFS_DECIMAL",      1,   8),
> -              "hexadecimal":        ("MFS_HEXADECIMAL",  1, 127),
> -              "ct state":           ("MFS_CT_STATE",     4,   4),
> -              "Ethernet":           ("MFS_ETHERNET",     6,   6),
> -              "IPv4":               ("MFS_IPV4",         4,   4),
> -              "IPv6":               ("MFS_IPV6",        16,  16),
> -              "OpenFlow 1.0 port":  ("MFS_OFP_PORT",     2,   2),
> -              "OpenFlow 1.1+ port": ("MFS_OFP_PORT_OXM", 4,   4),
> -              "frag":               ("MFS_FRAG",         1,   1),
> -              "tunnel flags":       ("MFS_TNL_FLAGS",    2,   2),
> -              "TCP flags":          ("MFS_TCP_FLAGS",    2,   2),
> -              "packet type":        ("MFS_PACKET_TYPE",  4,   4)}
> -
> -PREREQS = {"none": "MFP_NONE",
> -           "Ethernet": "MFP_ETHERNET",
> -           "ARP": "MFP_ARP",
> -           "VLAN VID": "MFP_VLAN_VID",
> -           "IPv4": "MFP_IPV4",
> -           "IPv6": "MFP_IPV6",
> -           "IPv4/IPv6": "MFP_IP_ANY",
> -           "NSH": "MFP_NSH",
> -           "CT": "MFP_CT_VALID",
> -           "MPLS": "MFP_MPLS",
> -           "TCP": "MFP_TCP",
> -           "UDP": "MFP_UDP",
> -           "SCTP": "MFP_SCTP",
> -           "ICMPv4": "MFP_ICMPV4",
> -           "ICMPv6": "MFP_ICMPV6",
> -           "ND": "MFP_ND",
> -           "ND solicit": "MFP_ND_SOLICIT",
> -           "ND advert": "MFP_ND_ADVERT"}
> -
> -# Maps a name prefix into an (experimenter ID, class) pair, so:
> -#
> -#      - Standard OXM classes are written as (0, <oxm_class>)
> -#
> -#      - Experimenter OXM classes are written as (<oxm_vender>, 0xffff)
> -#
> -# If a name matches more than one prefix, the longest one is used.
> -OXM_CLASSES = {"NXM_OF_":        (0,          0x0000, 'extension'),
> -               "NXM_NX_":        (0,          0x0001, 'extension'),
> -               "NXOXM_NSH_":     (0x005ad650, 0xffff, 'extension'),
> -               "OXM_OF_":        (0,          0x8000, 'standard'),
> -               "OXM_OF_PKT_REG": (0,          0x8001, 'standard'),
> -               "ONFOXM_ET_":     (0x4f4e4600, 0xffff, 'standard'),
> -               "ERICOXM_OF_":    (0,          0x1000, 'extension'),
> -
> -               # This is the experimenter OXM class for Nicira, which is the
> -               # one that OVS would be using instead of NXM_OF_ and NXM_NX_
> -               # if OVS didn't have those grandfathered in.  It is currently
> -               # used only to test support for experimenter OXM, since there
> -               # are barely any real uses of experimenter OXM in the wild.
> -               "NXOXM_ET_":      (0x00002320, 0xffff, 'extension')}
> +VERSION_REVERSE = dict((v,k) for k, v in VERSION.items())

Space after v, so; dict((v, k) for k, v in VERSION.items())

>  def oxm_name_to_class(name):
>      prefix = ''
> @@ -95,39 +33,6 @@ def is_standard_oxm(name):
>      return oxm_class_type == 'standard'
>
>
> -def decode_version_range(range):
> -    if range in VERSION:
> -        return (VERSION[range], VERSION[range])
> -    elif range.endswith('+'):
> -        return (VERSION[range[:-1]], max(VERSION.values()))
> -    else:
> -        a, b = re.match(r'^([^-]+)-([^-]+)$', range).groups()
> -        return (VERSION[a], VERSION[b])
> -
> -
> -def get_line():
> -    global line
> -    global line_number
> -    line = input_file.readline()
> -    line_number += 1
> -    if line == "":
> -        fatal("unexpected end of input")
> -
> -
> -n_errors = 0
> -
> -
> -def error(msg):
> -    global n_errors
> -    sys.stderr.write("%s:%d: %s\n" % (file_name, line_number, msg))
> -    n_errors += 1
> -
> -
> -def fatal(msg):
> -    error(msg)
> -    sys.exit(1)
> -
> -
>  def usage():
>      argv0 = os.path.basename(sys.argv[0])
>      print('''\
> @@ -141,172 +46,6 @@ file to #include.\
>      sys.exit(0)
>
>
> -def make_sizeof(s):
> -    m = re.match(r'(.*) up to (.*)', s)
> -    if m:
> -        struct, member = m.groups()
> -        return "offsetof(%s, %s)" % (struct, member)
> -    else:
> -        return "sizeof(%s)" % s
> -
> -
> -def parse_oxms(s, prefix, n_bytes):
> -    if s == 'none':
> -        return ()
> -
> -    return tuple(parse_oxm(s2.strip(), prefix, n_bytes) for s2 in 
> s.split(','))
> -
> -
> -match_types = dict()
> -
> -
> -def parse_oxm(s, prefix, n_bytes):
> -    global match_types
> -
> -    m = re.match('([A-Z0-9_]+)\(([0-9]+)\) since(?: OF(1\.[0-9]+) and)? 
> v([12]\.[0-9]+)$', s)
> -    if not m:
> -        fatal("%s: syntax error parsing %s" % (s, prefix))
> -
> -    name, oxm_type, of_version, ovs_version = m.groups()
> -
> -    class_ = oxm_name_to_class(name)
> -    if class_ is None:
> -        fatal("unknown OXM class for %s" % name)
> -    oxm_vendor, oxm_class, oxm_class_type = class_
> -
> -    if class_ in match_types:
> -        if oxm_type in match_types[class_]:
> -            fatal("duplicate match type for %s (conflicts with %s)" %
> -                  (name, match_types[class_][oxm_type]))
> -    else:
> -        match_types[class_] = dict()
> -    match_types[class_][oxm_type] = name
> -
> -    # Normally the oxm_length is the size of the field, but for experimenter
> -    # OXMs oxm_length also includes the 4-byte experimenter ID.
> -    oxm_length = n_bytes
> -    if oxm_class == 0xffff:
> -        oxm_length += 4
> -
> -    header = (oxm_vendor, oxm_class, int(oxm_type), oxm_length)
> -
> -    if of_version:
> -        if oxm_class_type == 'extension':
> -            fatal("%s: OXM extension can't have OpenFlow version" % name)
> -        if of_version not in VERSION:
> -            fatal("%s: unknown OpenFlow version %s" % (name, of_version))
> -        of_version_nr = VERSION[of_version]
> -        if of_version_nr < VERSION['1.2']:
> -            fatal("%s: claimed version %s predates OXM" % (name, of_version))
> -    else:
> -        if oxm_class_type == 'standard':
> -            fatal("%s: missing OpenFlow version number" % name)
> -        of_version_nr = 0
> -
> -    return (header, name, of_version_nr, ovs_version)
> -
> -
> -def parse_field(mff, comment):
> -    f = {'mff': mff}
> -
> -    # First line of comment is the field name.
> -    m = re.match(r'"([^"]+)"(?:\s+\(aka "([^"]+)"\))?(?:\s+\(.*\))?\.', 
> comment[0])
> -    if not m:
> -        fatal("%s lacks field name" % mff)
> -    f['name'], f['extra_name'] = m.groups()
> -
> -    # Find the last blank line the comment.  The field definitions
> -    # start after that.
> -    blank = None
> -    for i in range(len(comment)):
> -        if not comment[i]:
> -            blank = i
> -    if not blank:
> -        fatal("%s: missing blank line in comment" % mff)
> -
> -    d = {}
> -    for key in ("Type", "Maskable", "Formatting", "Prerequisites",
> -                "Access", "Prefix lookup member",
> -                "OXM", "NXM", "OF1.0", "OF1.1"):
> -        d[key] = None
> -    for fline in comment[blank + 1:]:
> -        m = re.match(r'([^:]+):\s+(.*)\.$', fline)
> -        if not m:
> -            fatal("%s: syntax error parsing key-value pair as part of %s"
> -                  % (fline, mff))
> -        key, value = m.groups()
> -        if key not in d:
> -            fatal("%s: unknown key" % key)
> -        elif key == 'Code point':
> -            d[key] += [value]
> -        elif d[key] is not None:
> -            fatal("%s: duplicate key" % key)
> -        d[key] = value
> -    for key, value in d.items():
> -        if not value and key not in ("OF1.0", "OF1.1",
> -                                     "Prefix lookup member", "Notes"):
> -            fatal("%s: missing %s" % (mff, key))
> -
> -    m = re.match(r'([a-zA-Z0-9]+)(?: \(low ([0-9]+) bits\))?$', d['Type'])
> -    if not m:
> -        fatal("%s: syntax error in type" % mff)
> -    type_ = m.group(1)
> -    if type_ not in TYPES:
> -        fatal("%s: unknown type %s" % (mff, d['Type']))
> -
> -    f['n_bytes'] = TYPES[type_][0]
> -    if m.group(2):
> -        f['n_bits'] = int(m.group(2))
> -        if f['n_bits'] > f['n_bytes'] * 8:
> -            fatal("%s: more bits (%d) than field size (%d)"
> -                  % (mff, f['n_bits'], 8 * f['n_bytes']))
> -    else:
> -        f['n_bits'] = 8 * f['n_bytes']
> -    f['variable'] = TYPES[type_][1]
> -
> -    if d['Maskable'] == 'no':
> -        f['mask'] = 'MFM_NONE'
> -    elif d['Maskable'] == 'bitwise':
> -        f['mask'] = 'MFM_FULLY'
> -    else:
> -        fatal("%s: unknown maskable %s" % (mff, d['Maskable']))
> -
> -    fmt = FORMATTING.get(d['Formatting'])
> -    if not fmt:
> -        fatal("%s: unknown format %s" % (mff, d['Formatting']))
> -    f['formatting'] = d['Formatting']
> -    if f['n_bytes'] < fmt[1] or f['n_bytes'] > fmt[2]:
> -        fatal("%s: %d-byte field can't be formatted as %s"
> -              % (mff, f['n_bytes'], d['Formatting']))
> -    f['string'] = fmt[0]
> -
> -    f['prereqs'] = d['Prerequisites']
> -    if f['prereqs'] not in PREREQS:
> -        fatal("%s: unknown prerequisites %s" % (mff, d['Prerequisites']))
> -
> -    if d['Access'] == 'read-only':
> -        f['writable'] = False
> -    elif d['Access'] == 'read/write':
> -        f['writable'] = True
> -    else:
> -        fatal("%s: unknown access %s" % (mff, d['Access']))
> -
> -    f['OF1.0'] = d['OF1.0']
> -    if not d['OF1.0'] in (None, 'exact match', 'CIDR mask'):
> -        fatal("%s: unknown OF1.0 match type %s" % (mff, d['OF1.0']))
> -
> -    f['OF1.1'] = d['OF1.1']
> -    if not d['OF1.1'] in (None, 'exact match', 'bitwise mask'):
> -        fatal("%s: unknown OF1.1 match type %s" % (mff, d['OF1.1']))
> -
> -    f['OXM'] = (parse_oxms(d['OXM'], 'OXM', f['n_bytes']) +
> -                parse_oxms(d['NXM'], 'NXM', f['n_bytes']))
> -
> -    f['prefix'] = d["Prefix lookup member"]
> -
> -    return f
> -
> -
>  def protocols_to_c(protocols):
>      if protocols == set(['of10', 'of11', 'oxm']):
>          return 'OFPUTIL_P_ANY'
> @@ -417,120 +156,6 @@ def make_nx_match(meta_flow_h):
>      for oline in output:
>          print(oline)
>
> -
> -def extract_ofp_fields(fn):
> -    global file_name
> -    global input_file
> -    global line_number
> -    global line
> -
> -    file_name = fn
> -    input_file = open(file_name)
> -    line_number = 0
> -
> -    fields = []
> -
> -    while True:
> -        get_line()
> -        if re.match('enum.*mf_field_id', line):
> -            break
> -
> -    while True:
> -        get_line()
> -        first_line_number = line_number
> -        here = '%s:%d' % (file_name, line_number)
> -        if (line.startswith('/*')
> -            or line.startswith(' *')
> -            or line.startswith('#')
> -            or not line
> -            or line.isspace()):
> -            continue
> -        elif re.match('}', line) or re.match('\s+MFF_N_IDS', line):
> -            break
> -
> -        # Parse the comment preceding an MFF_ constant into 'comment',
> -        # one line to an array element.
> -        line = line.strip()
> -        if not line.startswith('/*'):
> -            fatal("unexpected syntax between fields")
> -        line = line[1:]
> -        comment = []
> -        end = False
> -        while not end:
> -            line = line.strip()
> -            if line.startswith('*/'):
> -                get_line()
> -                break
> -            if not line.startswith('*'):
> -                fatal("unexpected syntax within field")
> -
> -            line = line[1:]
> -            if line.startswith(' '):
> -                line = line[1:]
> -            if line.startswith(' ') and comment:
> -                continuation = True
> -                line = line.lstrip()
> -            else:
> -                continuation = False
> -
> -            if line.endswith('*/'):
> -                line = line[:-2].rstrip()
> -                end = True
> -            else:
> -                end = False
> -
> -            if continuation:
> -                comment[-1] += " " + line
> -            else:
> -                comment += [line]
> -            get_line()
> -
> -        # Drop blank lines at each end of comment.
> -        while comment and not comment[0]:
> -            comment = comment[1:]
> -        while comment and not comment[-1]:
> -            comment = comment[:-1]
> -
> -        # Parse the MFF_ constant(s).
> -        mffs = []
> -        while True:
> -            m = re.match('\s+(MFF_[A-Z0-9_]+),?\s?$', line)
> -            if not m:
> -                break
> -            mffs += [m.group(1)]
> -            get_line()
> -        if not mffs:
> -            fatal("unexpected syntax looking for MFF_ constants")
> -
> -        if len(mffs) > 1 or '<N>' in comment[0]:
> -            for mff in mffs:
> -                # Extract trailing integer.
> -                m = re.match('.*[^0-9]([0-9]+)$', mff)
> -                if not m:
> -                    fatal("%s lacks numeric suffix in register group" % mff)
> -                n = m.group(1)
> -
> -                # Search-and-replace <N> within the comment,
> -                # and drop lines that have <x> for x != n.
> -                instance = []
> -                for x in comment:
> -                    y = x.replace('<N>', n)
> -                    if re.search('<[0-9]+>', y):
> -                        if ('<%s>' % n) not in y:
> -                            continue
> -                        y = re.sub('<[0-9]+>', '', y)
> -                    instance += [y.strip()]
> -                fields += [parse_field(mff, instance)]
> -        else:
> -            fields += [parse_field(mffs[0], comment)]
> -        continue
> -
> -    input_file.close()
> -
> -    if n_errors:
> -        sys.exit(1)
> -
> -    return fields
>  
>  ## ------------------------ ##
>  ## Documentation Generation ##
> diff --git a/python/automake.mk b/python/automake.mk
> index a3265292d..b869eb355 100644
> --- a/python/automake.mk
> +++ b/python/automake.mk
> @@ -52,7 +52,8 @@ ovs_pyfiles = \
>  EXTRA_DIST += \
>       python/build/__init__.py \
>       python/build/nroff.py \
> -     python/build/soutil.py
> +     python/build/soutil.py \
> +     python/build/extract_ofp_fields.py
>
>  # PyPI support.
>  EXTRA_DIST += \
> diff --git a/python/build/extract_ofp_fields.py 
> b/python/build/extract_ofp_fields.py
> new file mode 100644
> index 000000000..f6938b6dd
> --- /dev/null
> +++ b/python/build/extract_ofp_fields.py

As the below is just a move, I’m not do a full review, however I do think we 
should fix the flake8 errors:

$ flake8 extract_ofp_fields.py
extract_ofp_fields.py:1:1: F401 'getopt' imported but unused
extract_ofp_fields.py:3:1: F401 'os.path' imported but unused
extract_ofp_fields.py:5:1: F401 'xml.dom.minidom' imported but unused
extract_ofp_fields.py:6:1: F401 'build.nroff' imported but unused
extract_ofp_fields.py:17:26: E231 missing whitespace after ','
extract_ofp_fields.py:81:1: E302 expected 2 blank lines, found 1
extract_ofp_fields.py:118:1: E302 expected 2 blank lines, found 1
extract_ofp_fields.py:131:31: W605 invalid escape sequence '\('
extract_ofp_fields.py:131:41: W605 invalid escape sequence '\)'
extract_ofp_fields.py:131:57: W605 invalid escape sequence '\.'
extract_ofp_fields.py:131:79: W605 invalid escape sequence '\.'
extract_ofp_fields.py:131:80: E501 line too long (93 > 79 characters)
extract_ofp_fields.py:178:80: E501 line too long (83 > 79 characters)
extract_ofp_fields.py:274:1: E302 expected 2 blank lines, found 1
extract_ofp_fields.py:293:9: F841 local variable 'first_line_number' is 
assigned to but never used
extract_ofp_fields.py:294:9: F841 local variable 'here' is assigned to but 
never used
extract_ofp_fields.py:299:13: E129 visually indented line with same indent as 
next logical line
extract_ofp_fields.py:301:47: W605 invalid escape sequence '\s'
extract_ofp_fields.py:350:27: W605 invalid escape sequence '\s'
extract_ofp_fields.py:350:48: W605 invalid escape sequence '\s'

And probably add the file to FLAKE8_PYFILES.


> @@ -0,0 +1,386 @@
> +import getopt
> +import sys
> +import os.path
> +import re
> +import xml.dom.minidom
> +import build.nroff
> +
> +line = ""
> +
> +# Maps from user-friendly version number to its protocol encoding.
> +VERSION = {"1.0": 0x01,
> +           "1.1": 0x02,
> +           "1.2": 0x03,
> +           "1.3": 0x04,
> +           "1.4": 0x05,
> +           "1.5": 0x06}
> +VERSION_REVERSE = dict((v,k) for k, v in VERSION.items())
> +
> +TYPES = {"u8":       (1,   False),
> +         "be16":     (2,   False),
> +         "be32":     (4,   False),
> +         "MAC":      (6,   False),
> +         "be64":     (8,   False),
> +         "be128":    (16,  False),
> +         "tunnelMD": (124, True)}
> +
> +FORMATTING = {"decimal":            ("MFS_DECIMAL",      1,   8),
> +              "hexadecimal":        ("MFS_HEXADECIMAL",  1, 127),
> +              "ct state":           ("MFS_CT_STATE",     4,   4),
> +              "Ethernet":           ("MFS_ETHERNET",     6,   6),
> +              "IPv4":               ("MFS_IPV4",         4,   4),
> +              "IPv6":               ("MFS_IPV6",        16,  16),
> +              "OpenFlow 1.0 port":  ("MFS_OFP_PORT",     2,   2),
> +              "OpenFlow 1.1+ port": ("MFS_OFP_PORT_OXM", 4,   4),
> +              "frag":               ("MFS_FRAG",         1,   1),
> +              "tunnel flags":       ("MFS_TNL_FLAGS",    2,   2),
> +              "TCP flags":          ("MFS_TCP_FLAGS",    2,   2),
> +              "packet type":        ("MFS_PACKET_TYPE",  4,   4)}
> +
> +PREREQS = {"none": "MFP_NONE",
> +           "Ethernet": "MFP_ETHERNET",
> +           "ARP": "MFP_ARP",
> +           "VLAN VID": "MFP_VLAN_VID",
> +           "IPv4": "MFP_IPV4",
> +           "IPv6": "MFP_IPV6",
> +           "IPv4/IPv6": "MFP_IP_ANY",
> +           "NSH": "MFP_NSH",
> +           "CT": "MFP_CT_VALID",
> +           "MPLS": "MFP_MPLS",
> +           "TCP": "MFP_TCP",
> +           "UDP": "MFP_UDP",
> +           "SCTP": "MFP_SCTP",
> +           "ICMPv4": "MFP_ICMPV4",
> +           "ICMPv6": "MFP_ICMPV6",
> +           "ND": "MFP_ND",
> +           "ND solicit": "MFP_ND_SOLICIT",
> +           "ND advert": "MFP_ND_ADVERT"}
> +
> +# Maps a name prefix into an (experimenter ID, class) pair, so:
> +#
> +#      - Standard OXM classes are written as (0, <oxm_class>)
> +#
> +#      - Experimenter OXM classes are written as (<oxm_vender>, 0xffff)
> +#
> +# If a name matches more than one prefix, the longest one is used.
> +OXM_CLASSES = {"NXM_OF_":        (0,          0x0000, 'extension'),
> +               "NXM_NX_":        (0,          0x0001, 'extension'),
> +               "NXOXM_NSH_":     (0x005ad650, 0xffff, 'extension'),
> +               "OXM_OF_":        (0,          0x8000, 'standard'),
> +               "OXM_OF_PKT_REG": (0,          0x8001, 'standard'),
> +               "ONFOXM_ET_":     (0x4f4e4600, 0xffff, 'standard'),
> +               "ERICOXM_OF_":    (0,          0x1000, 'extension'),
> +
> +               # This is the experimenter OXM class for Nicira, which is the
> +               # one that OVS would be using instead of NXM_OF_ and NXM_NX_
> +               # if OVS didn't have those grandfathered in.  It is currently
> +               # used only to test support for experimenter OXM, since there
> +               # are barely any real uses of experimenter OXM in the wild.
> +               "NXOXM_ET_":      (0x00002320, 0xffff, 'extension')}
> +
> +def oxm_name_to_class(name):
> +    prefix = ''
> +    class_ = None
> +    for p, c in OXM_CLASSES.items():
> +        if name.startswith(p) and len(p) > len(prefix):
> +            prefix = p
> +            class_ = c
> +    return class_
> +
> +
> +def is_standard_oxm(name):
> +    oxm_vendor, oxm_class, oxm_class_type = oxm_name_to_class(name)
> +    return oxm_class_type == 'standard'
> +
> +
> +def get_line():
> +    global line
> +    global line_number
> +    line = input_file.readline()
> +    line_number += 1
> +    if line == "":
> +        fatal("unexpected end of input")
> +
> +
> +n_errors = 0
> +
> +
> +def error(msg):
> +    global n_errors
> +    sys.stderr.write("%s:%d: %s\n" % (file_name, line_number, msg))
> +    n_errors += 1
> +
> +
> +def fatal(msg):
> +    error(msg)
> +    sys.exit(1)
> +
> +def parse_oxms(s, prefix, n_bytes):
> +    if s == 'none':
> +        return ()
> +
> +    return tuple(parse_oxm(s2.strip(), prefix, n_bytes) for s2 in 
> s.split(','))
> +
> +
> +match_types = dict()
> +
> +
> +def parse_oxm(s, prefix, n_bytes):
> +    global match_types
> +
> +    m = re.match('([A-Z0-9_]+)\(([0-9]+)\) since(?: OF(1\.[0-9]+) and)? 
> v([12]\.[0-9]+)$', s)
> +    if not m:
> +        fatal("%s: syntax error parsing %s" % (s, prefix))
> +
> +    name, oxm_type, of_version, ovs_version = m.groups()
> +
> +    class_ = oxm_name_to_class(name)
> +    if class_ is None:
> +        fatal("unknown OXM class for %s" % name)
> +    oxm_vendor, oxm_class, oxm_class_type = class_
> +
> +    if class_ in match_types:
> +        if oxm_type in match_types[class_]:
> +            fatal("duplicate match type for %s (conflicts with %s)" %
> +                  (name, match_types[class_][oxm_type]))
> +    else:
> +        match_types[class_] = dict()
> +    match_types[class_][oxm_type] = name
> +
> +    # Normally the oxm_length is the size of the field, but for experimenter
> +    # OXMs oxm_length also includes the 4-byte experimenter ID.
> +    oxm_length = n_bytes
> +    if oxm_class == 0xffff:
> +        oxm_length += 4
> +
> +    header = (oxm_vendor, oxm_class, int(oxm_type), oxm_length)
> +
> +    if of_version:
> +        if oxm_class_type == 'extension':
> +            fatal("%s: OXM extension can't have OpenFlow version" % name)
> +        if of_version not in VERSION:
> +            fatal("%s: unknown OpenFlow version %s" % (name, of_version))
> +        of_version_nr = VERSION[of_version]
> +        if of_version_nr < VERSION['1.2']:
> +            fatal("%s: claimed version %s predates OXM" % (name, of_version))
> +    else:
> +        if oxm_class_type == 'standard':
> +            fatal("%s: missing OpenFlow version number" % name)
> +        of_version_nr = 0
> +
> +    return (header, name, of_version_nr, ovs_version)
> +
> +
> +def parse_field(mff, comment):
> +    f = {'mff': mff}
> +
> +    # First line of comment is the field name.
> +    m = re.match(r'"([^"]+)"(?:\s+\(aka "([^"]+)"\))?(?:\s+\(.*\))?\.', 
> comment[0])
> +    if not m:
> +        fatal("%s lacks field name" % mff)
> +    f['name'], f['extra_name'] = m.groups()
> +
> +    # Find the last blank line the comment.  The field definitions
> +    # start after that.
> +    blank = None
> +    for i in range(len(comment)):
> +        if not comment[i]:
> +            blank = i
> +    if not blank:
> +        fatal("%s: missing blank line in comment" % mff)
> +
> +    d = {}
> +    for key in ("Type", "Maskable", "Formatting", "Prerequisites",
> +                "Access", "Prefix lookup member",
> +                "OXM", "NXM", "OF1.0", "OF1.1"):
> +        d[key] = None
> +    for fline in comment[blank + 1:]:
> +        m = re.match(r'([^:]+):\s+(.*)\.$', fline)
> +        if not m:
> +            fatal("%s: syntax error parsing key-value pair as part of %s"
> +                  % (fline, mff))
> +        key, value = m.groups()
> +        if key not in d:
> +            fatal("%s: unknown key" % key)
> +        elif key == 'Code point':
> +            d[key] += [value]
> +        elif d[key] is not None:
> +            fatal("%s: duplicate key" % key)
> +        d[key] = value
> +    for key, value in d.items():
> +        if not value and key not in ("OF1.0", "OF1.1",
> +                                     "Prefix lookup member", "Notes"):
> +            fatal("%s: missing %s" % (mff, key))
> +
> +    m = re.match(r'([a-zA-Z0-9]+)(?: \(low ([0-9]+) bits\))?$', d['Type'])
> +    if not m:
> +        fatal("%s: syntax error in type" % mff)
> +    type_ = m.group(1)
> +    if type_ not in TYPES:
> +        fatal("%s: unknown type %s" % (mff, d['Type']))
> +
> +    f['n_bytes'] = TYPES[type_][0]
> +    if m.group(2):
> +        f['n_bits'] = int(m.group(2))
> +        if f['n_bits'] > f['n_bytes'] * 8:
> +            fatal("%s: more bits (%d) than field size (%d)"
> +                  % (mff, f['n_bits'], 8 * f['n_bytes']))
> +    else:
> +        f['n_bits'] = 8 * f['n_bytes']
> +    f['variable'] = TYPES[type_][1]
> +
> +    if d['Maskable'] == 'no':
> +        f['mask'] = 'MFM_NONE'
> +    elif d['Maskable'] == 'bitwise':
> +        f['mask'] = 'MFM_FULLY'
> +    else:
> +        fatal("%s: unknown maskable %s" % (mff, d['Maskable']))
> +
> +    fmt = FORMATTING.get(d['Formatting'])
> +    if not fmt:
> +        fatal("%s: unknown format %s" % (mff, d['Formatting']))
> +    f['formatting'] = d['Formatting']
> +    if f['n_bytes'] < fmt[1] or f['n_bytes'] > fmt[2]:
> +        fatal("%s: %d-byte field can't be formatted as %s"
> +              % (mff, f['n_bytes'], d['Formatting']))
> +    f['string'] = fmt[0]
> +
> +    f['prereqs'] = d['Prerequisites']
> +    if f['prereqs'] not in PREREQS:
> +        fatal("%s: unknown prerequisites %s" % (mff, d['Prerequisites']))
> +
> +    if d['Access'] == 'read-only':
> +        f['writable'] = False
> +    elif d['Access'] == 'read/write':
> +        f['writable'] = True
> +    else:
> +        fatal("%s: unknown access %s" % (mff, d['Access']))
> +
> +    f['OF1.0'] = d['OF1.0']
> +    if not d['OF1.0'] in (None, 'exact match', 'CIDR mask'):
> +        fatal("%s: unknown OF1.0 match type %s" % (mff, d['OF1.0']))
> +
> +    f['OF1.1'] = d['OF1.1']
> +    if not d['OF1.1'] in (None, 'exact match', 'bitwise mask'):
> +        fatal("%s: unknown OF1.1 match type %s" % (mff, d['OF1.1']))
> +
> +    f['OXM'] = (parse_oxms(d['OXM'], 'OXM', f['n_bytes']) +
> +                parse_oxms(d['NXM'], 'NXM', f['n_bytes']))
> +
> +    f['prefix'] = d["Prefix lookup member"]
> +
> +    return f
> +
> +def extract_ofp_fields(fn):
> +    global file_name
> +    global input_file
> +    global line_number
> +    global line
> +
> +    file_name = fn
> +    input_file = open(file_name)
> +    line_number = 0
> +
> +    fields = []
> +
> +    while True:
> +        get_line()
> +        if re.match('enum.*mf_field_id', line):
> +            break
> +
> +    while True:
> +        get_line()
> +        first_line_number = line_number
> +        here = '%s:%d' % (file_name, line_number)
> +        if (line.startswith('/*')
> +            or line.startswith(' *')
> +            or line.startswith('#')
> +            or not line
> +            or line.isspace()):
> +            continue
> +        elif re.match('}', line) or re.match('\s+MFF_N_IDS', line):
> +            break
> +
> +        # Parse the comment preceding an MFF_ constant into 'comment',
> +        # one line to an array element.
> +        line = line.strip()
> +        if not line.startswith('/*'):
> +            fatal("unexpected syntax between fields")
> +        line = line[1:]
> +        comment = []
> +        end = False
> +        while not end:
> +            line = line.strip()
> +            if line.startswith('*/'):
> +                get_line()
> +                break
> +            if not line.startswith('*'):
> +                fatal("unexpected syntax within field")
> +
> +            line = line[1:]
> +            if line.startswith(' '):
> +                line = line[1:]
> +            if line.startswith(' ') and comment:
> +                continuation = True
> +                line = line.lstrip()
> +            else:
> +                continuation = False
> +
> +            if line.endswith('*/'):
> +                line = line[:-2].rstrip()
> +                end = True
> +            else:
> +                end = False
> +
> +            if continuation:
> +                comment[-1] += " " + line
> +            else:
> +                comment += [line]
> +            get_line()
> +
> +        # Drop blank lines at each end of comment.
> +        while comment and not comment[0]:
> +            comment = comment[1:]
> +        while comment and not comment[-1]:
> +            comment = comment[:-1]
> +
> +        # Parse the MFF_ constant(s).
> +        mffs = []
> +        while True:
> +            m = re.match('\s+(MFF_[A-Z0-9_]+),?\s?$', line)
> +            if not m:
> +                break
> +            mffs += [m.group(1)]
> +            get_line()
> +        if not mffs:
> +            fatal("unexpected syntax looking for MFF_ constants")
> +
> +        if len(mffs) > 1 or '<N>' in comment[0]:
> +            for mff in mffs:
> +                # Extract trailing integer.
> +                m = re.match('.*[^0-9]([0-9]+)$', mff)
> +                if not m:
> +                    fatal("%s lacks numeric suffix in register group" % mff)
> +                n = m.group(1)
> +
> +                # Search-and-replace <N> within the comment,
> +                # and drop lines that have <x> for x != n.
> +                instance = []
> +                for x in comment:
> +                    y = x.replace('<N>', n)
> +                    if re.search('<[0-9]+>', y):
> +                        if ('<%s>' % n) not in y:
> +                            continue
> +                        y = re.sub('<[0-9]+>', '', y)
> +                    instance += [y.strip()]
> +                fields += [parse_field(mff, instance)]
> +        else:
> +            fields += [parse_field(mffs[0], comment)]
> +        continue
> +
> +    input_file.close()
> +
> +    if n_errors:
> +        sys.exit(1)
> +
> +    return fields
> -- 
> 2.31.1

_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev

Reply via email to