extract-ofp-errors: Make Python 3 compatible.
[sliver-openvswitch.git] / build-aux / extract-ofp-errors
1 #! /usr/bin/python
2
3 import sys
4 import os.path
5 import re
6
7 macros = {}
8
9 token = None
10 line = ""
11 idRe = "[a-zA-Z_][a-zA-Z_0-9]*"
12 tokenRe = "#?" + idRe + "|[0-9]+|."
13 inComment = False
14 inDirective = False
15
16 def getLine():
17     global line
18     global lineNumber
19     line = inputFile.readline()
20     lineNumber += 1
21     if line == "":
22         fatal("unexpected end of input")
23
24 def getToken():
25     global token
26     global line
27     global inComment
28     global inDirective
29     while True:
30         line = line.lstrip()
31         if line != "":
32             if line.startswith("/*"):
33                 inComment = True
34                 line = line[2:]
35             elif inComment:
36                 commentEnd = line.find("*/")
37                 if commentEnd < 0:
38                     line = ""
39                 else:
40                     inComment = False
41                     line = line[commentEnd + 2:]
42             else:
43                 match = re.match(tokenRe, line)
44                 token = match.group(0)
45                 line = line[len(token):]
46                 if token.startswith('#'):
47                     inDirective = True
48                 elif token in macros and not inDirective:
49                     line = macros[token] + line
50                     continue
51                 return True
52         elif inDirective:
53             token = "$"
54             inDirective = False
55             return True
56         else:
57             global lineNumber
58             line = inputFile.readline()
59             lineNumber += 1
60             while line.endswith("\\\n"):
61                 line = line[:-2] + inputFile.readline()
62                 lineNumber += 1
63             if line == "":
64                 if token == None:
65                     fatal("unexpected end of input")
66                 token = None
67                 return False
68
69 n_errors = 0
70 def error(msg):
71     global n_errors
72     sys.stderr.write("%s:%d: %s\n" % (fileName, lineNumber, msg))
73     n_errors += 1
74
75 def fatal(msg):
76     error(msg)
77     sys.exit(1)
78
79 def skipDirective():
80     getToken()
81     while token != '$':
82         getToken()
83
84 def isId(s):
85     return re.match(idRe + "$", s) != None
86
87 def forceId():
88     if not isId(token):
89         fatal("identifier expected")
90
91 def forceInteger():
92     if not re.match('[0-9]+$', token):
93         fatal("integer expected")
94
95 def match(t):
96     if token == t:
97         getToken()
98         return True
99     else:
100         return False
101
102 def forceMatch(t):
103     if not match(t):
104         fatal("%s expected" % t)
105
106 def parseTaggedName():
107     assert token in ('struct', 'union')
108     name = token
109     getToken()
110     forceId()
111     name = "%s %s" % (name, token)
112     getToken()
113     return name
114
115 def print_enum(tag, constants, storage_class):
116     print ("""
117 %(storage_class)sconst char *
118 %(tag)s_to_string(uint16_t value)
119 {
120     switch (value) {\
121 """ % {"tag": tag,
122        "bufferlen": len(tag) + 32,
123        "storage_class": storage_class})
124     for constant in constants:
125         print ("    case %s: return \"%s\";" % (constant, constant))
126     print ("""\
127     }
128     return NULL;
129 }\
130 """ % {"tag": tag})
131
132 def usage():
133     argv0 = os.path.basename(sys.argv[0])
134     print ('''\
135 %(argv0)s, for extracting OpenFlow error codes from header files
136 usage: %(argv0)s FILE [FILE...]
137
138 This program reads the header files specified on the command line and
139 outputs a C source file for translating OpenFlow error codes into
140 strings, for use as lib/ofp-errors.c in the Open vSwitch source tree.
141
142 This program is specialized for reading lib/ofp-errors.h.  It will not
143 work on arbitrary header files without extensions.\
144 ''' % {"argv0": argv0})
145     sys.exit(0)
146
147 def extract_ofp_errors(filenames):
148     error_types = {}
149
150     comments = []
151     names = []
152     domain = {}
153     reverse = {}
154     for domain_name in ("OF1.0", "OF1.1", "OF1.2", "OF1.3",
155                         "NX1.0", "NX1.1", "NX1.2", "NX1.3"):
156         domain[domain_name] = {}
157         reverse[domain_name] = {}
158
159     n_errors = 0
160     expected_errors = {}
161
162     global fileName
163     for fileName in filenames:
164         global inputFile
165         global lineNumber
166         inputFile = open(fileName)
167         lineNumber = 0
168
169         while True:
170             getLine()
171             if re.match('enum ofperr', line):
172                 break
173
174         while True:
175             getLine()
176             if line.startswith('/*') or not line or line.isspace():
177                 continue
178             elif re.match('}', line):
179                 break
180
181             if not line.lstrip().startswith('/*'):
182                 fatal("unexpected syntax between errors")
183
184             comment = line.lstrip()[2:].strip()
185             while not comment.endswith('*/'):
186                 getLine()
187                 if line.startswith('/*') or not line or line.isspace():
188                     fatal("unexpected syntax within error")
189                 comment += ' %s' % line.lstrip('* \t').rstrip(' \t\r\n')
190             comment = comment[:-2].rstrip()
191
192             m = re.match('Expected: (.*)\.$', comment)
193             if m:
194                 expected_errors[m.group(1)] = (fileName, lineNumber)
195                 continue
196
197             m = re.match('((?:.(?!\.  ))+.)\.  (.*)$', comment)
198             if not m:
199                 fatal("unexpected syntax between errors")
200
201             dsts, comment = m.groups()
202
203             getLine()
204             m = re.match('\s+(?:OFPERR_((?:OFP|NX)[A-Z0-9_]+))(\s*=\s*OFPERR_OFS)?,',
205                          line)
206             if not m:
207                 fatal("syntax error expecting enum value")
208
209             enum = m.group(1)
210
211             comments.append(re.sub('\[[^]]*\]', '', comment))
212             names.append(enum)
213
214             for dst in dsts.split(', '):
215                 m = re.match(r'([A-Z0-9.+]+)\((\d+|(0x)[0-9a-fA-F]+)(?:,(\d+))?\)$', dst)
216                 if not m:
217                     fatal("%s: syntax error in destination" % dst)
218                 targets = m.group(1)
219                 if m.group(3):
220                     base = 16
221                 else:
222                     base = 10
223                 type_ = int(m.group(2), base)
224                 if m.group(4):
225                     code = int(m.group(4))
226                 else:
227                     code = None
228
229                 target_map = {"OF1.0+": ("OF1.0", "OF1.1", "OF1.2", "OF1.3"),
230                               "OF1.1+": ("OF1.1", "OF1.2", "OF1.3"),
231                               "OF1.2+": ("OF1.2", "OF1.3"),
232                               "OF1.3+": ("OF1.3",),
233                               "OF1.0":  ("OF1.0",),
234                               "OF1.1":  ("OF1.1",),
235                               "OF1.2":  ("OF1.2",),
236                               "OF1.3":  ("OF1.3",),
237                               "NX1.0+": ("OF1.0", "OF1.1", "OF1.2", "OF1.3"),
238                               "NX1.1+": ("OF1.1", "OF1.2", "OF1.3"),
239                               "NX1.2+": ("OF1.2", "OF1.3"),
240                               "NX1.3+": ("OF1.3",),
241                               "NX1.0":  ("OF1.0",),
242                               "NX1.1":  ("OF1.1",),
243                               "NX1.2":  ("OF1.2",),
244                               "NX1.3":  ("OF1.3",)}
245                 if targets not in target_map:
246                     fatal("%s: unknown error domain" % targets)
247                 if targets.startswith('NX') and code < 0x100:
248                     fatal("%s: NX domain code cannot be less than 0x100" % dst)
249                 if targets.startswith('OF') and code >= 0x100:
250                     fatal("%s: OF domain code cannot be greater than 0x100"
251                           % dst)
252                 for target in target_map[targets]:
253                     domain[target].setdefault(type_, {})
254                     if code in domain[target][type_]:
255                         msg = "%d,%d in %s means both %s and %s" % (
256                             type_, code, target,
257                             domain[target][type_][code][0], enum)
258                         if msg in expected_errors:
259                             del expected_errors[msg]
260                         else:
261                             error("%s: %s." % (dst, msg))
262                             sys.stderr.write("%s:%d: %s: Here is the location "
263                                              "of the previous definition.\n"
264                                              % (domain[target][type_][code][1],
265                                                 domain[target][type_][code][2],
266                                                 dst))
267                     else:
268                         domain[target][type_][code] = (enum, fileName,
269                                                        lineNumber)
270
271                     if enum in reverse[target]:
272                         error("%s: %s in %s means both %d,%d and %d,%d." %
273                               (dst, enum, target,
274                                reverse[target][enum][0],
275                                reverse[target][enum][1],
276                                type_, code))
277                     reverse[target][enum] = (type_, code)
278
279         inputFile.close()
280
281     for fn, ln in expected_errors.values():
282         sys.stderr.write("%s:%d: expected duplicate not used.\n" % (fn, ln))
283         n_errors += 1
284
285     if n_errors:
286         sys.exit(1)
287
288     print ("""\
289 /* Generated automatically; do not modify!     -*- buffer-read-only: t -*- */
290
291 #define OFPERR_N_ERRORS %d
292
293 struct ofperr_domain {
294     const char *name;
295     uint8_t version;
296     enum ofperr (*decode)(uint16_t type, uint16_t code);
297     struct pair errors[OFPERR_N_ERRORS];
298 };
299
300 static const char *error_names[OFPERR_N_ERRORS] = {
301 %s
302 };
303
304 static const char *error_comments[OFPERR_N_ERRORS] = {
305 %s
306 };\
307 """ % (len(names),
308        '\n'.join('    "%s",' % name for name in names),
309        '\n'.join('    "%s",' % re.sub(r'(["\\])', r'\\\1', comment)
310                  for comment in comments)))
311
312     def output_domain(map, name, description, version):
313         print ("""
314 static enum ofperr
315 %s_decode(uint16_t type, uint16_t code)
316 {
317     switch ((type << 16) | code) {""" % name)
318         found = set()
319         for enum in names:
320             if enum not in map:
321                 continue
322             type_, code = map[enum]
323             if code is None:
324                 continue
325             value = (type_ << 16) | code
326             if value in found:
327                 continue
328             found.add(value)
329             print ("    case (%d << 16) | %d:" % (type_, code))
330             print ("        return OFPERR_%s;" % enum)
331         print ("""\
332     }
333
334     return 0;
335 }""")
336
337         print ("""
338 static const struct ofperr_domain %s = {
339     "%s",
340     %d,
341     %s_decode,
342     {""" % (name, description, version, name))
343         for enum in names:
344             if enum in map:
345                 type_, code = map[enum]
346                 if code == None:
347                     code = -1
348             else:
349                 type_ = code = -1
350             print ("        { %2d, %3d }, /* %s */" % (type_, code, enum))
351         print ("""\
352     },
353 };""")
354
355     output_domain(reverse["OF1.0"], "ofperr_of10", "OpenFlow 1.0", 0x01)
356     output_domain(reverse["OF1.1"], "ofperr_of11", "OpenFlow 1.1", 0x02)
357     output_domain(reverse["OF1.2"], "ofperr_of12", "OpenFlow 1.2", 0x03)
358     output_domain(reverse["OF1.3"], "ofperr_of13", "OpenFlow 1.3", 0x04)
359
360 if __name__ == '__main__':
361     if '--help' in sys.argv:
362         usage()
363     elif len(sys.argv) < 2:
364         sys.stderr.write("at least one non-option argument required; "
365                          "use --help for help\n")
366         sys.exit(1)
367     else:
368         extract_ofp_errors(sys.argv[1:])