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
