Setting tag sliver-openvswitch-2.2.90-1
[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 # Map from OpenFlow version number to version ID used in ofp_header.
10 version_map = {"1.0": 0x01,
11                "1.1": 0x02,
12                "1.2": 0x03,
13                "1.3": 0x04,
14                "1.4": 0x05}
15 version_reverse_map = dict((v, k) for (k, v) in version_map.iteritems())
16
17 token = None
18 line = ""
19 idRe = "[a-zA-Z_][a-zA-Z_0-9]*"
20 tokenRe = "#?" + idRe + "|[0-9]+|."
21 inComment = False
22 inDirective = False
23
24 def open_file(fn):
25     global fileName
26     global inputFile
27     global lineNumber
28     fileName = fn
29     inputFile = open(fileName)
30     lineNumber = 0
31
32 def tryGetLine():
33     global inputFile
34     global line
35     global lineNumber
36     line = inputFile.readline()
37     lineNumber += 1
38     return line != ""
39
40 def getLine():
41     if not tryGetLine():
42         fatal("unexpected end of input")
43
44 def getToken():
45     global token
46     global line
47     global inComment
48     global inDirective
49     while True:
50         line = line.lstrip()
51         if line != "":
52             if line.startswith("/*"):
53                 inComment = True
54                 line = line[2:]
55             elif inComment:
56                 commentEnd = line.find("*/")
57                 if commentEnd < 0:
58                     line = ""
59                 else:
60                     inComment = False
61                     line = line[commentEnd + 2:]
62             else:
63                 match = re.match(tokenRe, line)
64                 token = match.group(0)
65                 line = line[len(token):]
66                 if token.startswith('#'):
67                     inDirective = True
68                 elif token in macros and not inDirective:
69                     line = macros[token] + line
70                     continue
71                 return True
72         elif inDirective:
73             token = "$"
74             inDirective = False
75             return True
76         else:
77             global lineNumber
78             line = inputFile.readline()
79             lineNumber += 1
80             while line.endswith("\\\n"):
81                 line = line[:-2] + inputFile.readline()
82                 lineNumber += 1
83             if line == "":
84                 if token == None:
85                     fatal("unexpected end of input")
86                 token = None
87                 return False
88
89 n_errors = 0
90 def error(msg):
91     global n_errors
92     sys.stderr.write("%s:%d: %s\n" % (fileName, lineNumber, msg))
93     n_errors += 1
94
95 def fatal(msg):
96     error(msg)
97     sys.exit(1)
98
99 def skipDirective():
100     getToken()
101     while token != '$':
102         getToken()
103
104 def isId(s):
105     return re.match(idRe + "$", s) != None
106
107 def forceId():
108     if not isId(token):
109         fatal("identifier expected")
110
111 def forceInteger():
112     if not re.match('[0-9]+$', token):
113         fatal("integer expected")
114
115 def match(t):
116     if token == t:
117         getToken()
118         return True
119     else:
120         return False
121
122 def forceMatch(t):
123     if not match(t):
124         fatal("%s expected" % t)
125
126 def parseTaggedName():
127     assert token in ('struct', 'union')
128     name = token
129     getToken()
130     forceId()
131     name = "%s %s" % (name, token)
132     getToken()
133     return name
134
135 def print_enum(tag, constants, storage_class):
136     print ("""
137 %(storage_class)sconst char *
138 %(tag)s_to_string(uint16_t value)
139 {
140     switch (value) {\
141 """ % {"tag": tag,
142        "bufferlen": len(tag) + 32,
143        "storage_class": storage_class})
144     for constant in constants:
145         print ("    case %s: return \"%s\";" % (constant, constant))
146     print ("""\
147     }
148     return NULL;
149 }\
150 """ % {"tag": tag})
151
152 def usage():
153     argv0 = os.path.basename(sys.argv[0])
154     print ('''\
155 %(argv0)s, for extracting OpenFlow error codes from header files
156 usage: %(argv0)s ERROR_HEADER VENDOR_HEADER
157
158 This program reads VENDOR_HEADER to obtain OpenFlow vendor (aka
159 experimenter IDs), then ERROR_HEADER to obtain OpenFlow error number.
160 It outputs a C source file for translating OpenFlow error codes into
161 strings.
162
163 ERROR_HEADER should point to lib/ofp-errors.h.
164 VENDOR_HEADER should point to include/openflow/openflow-common.h.
165 The output is suitable for use as lib/ofp-errors.inc.\
166 ''' % {"argv0": argv0})
167     sys.exit(0)
168
169 def extract_vendor_ids(fn):
170     global vendor_map
171     vendor_map = {}
172     vendor_loc = {}
173
174     open_file(fn)
175     while tryGetLine():
176         m = re.match(r'#define\s+([A-Z0-9_]+)_VENDOR_ID\s+(0x[0-9a-fA-F]+|[0-9]+)', line)
177         if not m:
178             continue
179
180         name = m.group(1)
181         id_ = int(m.group(2), 0)
182
183         if name in vendor_map:
184             error("%s: duplicate definition of vendor" % name)
185             sys.stderr.write("%s: Here is the location of the previous "
186                              "definition.\n" % vendor_loc[name])
187             sys.exit(1)
188
189         vendor_map[name] = id_
190         vendor_loc[name] = "%s:%d" % (fileName, lineNumber)
191
192     if not vendor_map:
193         fatal("%s: no vendor definitions found" % fn)
194
195     inputFile.close()
196
197     vendor_reverse_map = {}
198     for name, id_ in vendor_map.items():
199         if id_ in vendor_reverse_map:
200             fatal("%s: duplicate vendor id for vendors %s and %s"
201                   % (id_, vendor_reverse_map[id_], name))
202         vendor_reverse_map[id_] = name
203
204 def extract_ofp_errors(fn):
205     error_types = {}
206
207     comments = []
208     names = []
209     domain = {}
210     reverse = {}
211     for domain_name in version_map.values():
212         domain[domain_name] = {}
213         reverse[domain_name] = {}
214
215     n_errors = 0
216     expected_errors = {}
217
218     open_file(fn)
219
220     while True:
221         getLine()
222         if re.match('enum ofperr', line):
223             break
224
225     while True:
226         getLine()
227         if line.startswith('/*') or not line or line.isspace():
228             continue
229         elif re.match('}', line):
230             break
231
232         if not line.lstrip().startswith('/*'):
233             fatal("unexpected syntax between errors")
234
235         comment = line.lstrip()[2:].strip()
236         while not comment.endswith('*/'):
237             getLine()
238             if line.startswith('/*') or not line or line.isspace():
239                 fatal("unexpected syntax within error")
240             comment += ' %s' % line.lstrip('* \t').rstrip(' \t\r\n')
241         comment = comment[:-2].rstrip()
242
243         m = re.match('Expected: (.*)\.$', comment)
244         if m:
245             expected_errors[m.group(1)] = (fileName, lineNumber)
246             continue
247
248         m = re.match('((?:.(?!\.  ))+.)\.  (.*)$', comment)
249         if not m:
250             fatal("unexpected syntax between errors")
251
252         dsts, comment = m.groups()
253
254         getLine()
255         m = re.match('\s+(?:OFPERR_([A-Z0-9_]+))(\s*=\s*OFPERR_OFS)?,',
256                      line)
257         if not m:
258             fatal("syntax error expecting enum value")
259
260         enum = m.group(1)
261         if enum in names:
262             fatal("%s specified twice" % enum)
263
264         comments.append(re.sub('\[[^]]*\]', '', comment))
265         names.append(enum)
266
267         for dst in dsts.split(', '):
268             m = re.match(r'([A-Z]+)([0-9.]+)(\+|-[0-9.]+)?\((\d+)(?:,(\d+))?\)$', dst)
269             if not m:
270                 fatal("%r: syntax error in destination" % dst)
271             vendor_name = m.group(1)
272             version1_name = m.group(2)
273             version2_name = m.group(3)
274             type_ = int(m.group(4))
275             if m.group(5):
276                 code = int(m.group(5))
277             else:
278                 code = None
279
280             if vendor_name not in vendor_map:
281                 fatal("%s: unknown vendor" % vendor_name)
282             vendor = vendor_map[vendor_name]
283
284             if version1_name not in version_map:
285                 fatal("%s: unknown OpenFlow version" % version1_name)
286             v1 = version_map[version1_name]
287
288             if version2_name is None:
289                 v2 = v1
290             elif version2_name == "+":
291                 v2 = max(version_map.values())
292             elif version2_name[1:] not in version_map:
293                 fatal("%s: unknown OpenFlow version" % version2_name[1:])
294             else:
295                 v2 = version_map[version2_name[1:]]
296
297             if v2 < v1:
298                 fatal("%s%s: %s precedes %s"
299                       % (version1_name, version2_name,
300                          version2_name, version1_name))
301
302             if vendor == vendor_map['NX']:
303                 if v1 >= version_map['1.2'] or v2 >= version_map['1.2']:
304                     if code is not None:
305                         fatal("%s: NX1.2+ domains do not have codes" % dst)
306                     code = 0
307             elif vendor != vendor_map['OF']:
308                 if code is not None:
309                     fatal("%s: %s domains do not have codes" % vendor_name)
310
311             for version in range(v1, v2 + 1):
312                 domain[version].setdefault(vendor, {})
313                 domain[version][vendor].setdefault(type_, {})
314                 if code in domain[version][vendor][type_]:
315                     msg = "%#x,%d,%d in OF%s means both %s and %s" % (
316                         vendor, type_, code, version_reverse_map[version],
317                         domain[version][vendor][type_][code][0], enum)
318                     if msg in expected_errors:
319                         del expected_errors[msg]
320                     else:
321                         error("%s: %s." % (dst, msg))
322                         sys.stderr.write("%s:%d: %s: Here is the location "
323                                          "of the previous definition.\n"
324                                          % (domain[version][vendor][type_][code][1],
325                                             domain[version][vendor][type_][code][2],
326                                             dst))
327                 else:
328                     domain[version][vendor][type_][code] = (enum, fileName,
329                                                    lineNumber)
330
331                 assert enum not in reverse[version]
332                 reverse[version][enum] = (vendor, type_, code)
333
334     inputFile.close()
335
336     for fn, ln in expected_errors.values():
337         sys.stderr.write("%s:%d: expected duplicate not used.\n" % (fn, ln))
338         n_errors += 1
339
340     if n_errors:
341         sys.exit(1)
342
343     print ("""\
344 /* Generated automatically; do not modify!     -*- buffer-read-only: t -*- */
345
346 #define OFPERR_N_ERRORS %d
347
348 struct ofperr_domain {
349     const char *name;
350     uint8_t version;
351     enum ofperr (*decode)(uint32_t vendor, uint16_t type, uint16_t code);
352     struct triplet errors[OFPERR_N_ERRORS];
353 };
354
355 static const char *error_names[OFPERR_N_ERRORS] = {
356 %s
357 };
358
359 static const char *error_comments[OFPERR_N_ERRORS] = {
360 %s
361 };\
362 """ % (len(names),
363        '\n'.join('    "%s",' % name for name in names),
364        '\n'.join('    "%s",' % re.sub(r'(["\\])', r'\\\1', comment)
365                  for comment in comments)))
366
367     def output_domain(map, name, description, version):
368         print ("""
369 static enum ofperr
370 %s_decode(uint32_t vendor, uint16_t type, uint16_t code)
371 {
372     switch (((uint64_t) vendor << 32) | (type << 16) | code) {""" % name)
373         found = set()
374         for enum in names:
375             if enum not in map:
376                 continue
377             vendor, type_, code = map[enum]
378             if code is None:
379                 continue
380             value = (vendor << 32) | (type_ << 16) | code
381             if value in found:
382                 continue
383             found.add(value)
384             if vendor:
385                 vendor_s = "(%#xULL << 32) | " % vendor
386             else:
387                 vendor_s = ""
388             print ("    case %s(%d << 16) | %d:" % (vendor_s, type_, code))
389             print ("        return OFPERR_%s;" % enum)
390         print ("""\
391     }
392
393     return 0;
394 }""")
395
396         print ("""
397 static const struct ofperr_domain %s = {
398     "%s",
399     %d,
400     %s_decode,
401     {""" % (name, description, version, name))
402         for enum in names:
403             if enum in map:
404                 vendor, type_, code = map[enum]
405                 if code == None:
406                     code = -1
407                 print "        { %#8x, %2d, %3d }, /* %s */" % (vendor, type_, code, enum)
408             else:
409                 print ("        {       -1, -1,  -1 }, /* %s */" % enum)
410         print ("""\
411     },
412 };""")
413
414     for version_name, id_ in version_map.items():
415         var = 'ofperr_of' + re.sub('[^A-Za-z0-9_]', '', version_name)
416         description = "OpenFlow %s" % version_name
417         output_domain(reverse[id_], var, description, id_)
418
419 if __name__ == '__main__':
420     if '--help' in sys.argv:
421         usage()
422     elif len(sys.argv) != 3:
423         sys.stderr.write("exactly two non-options arguments required; "
424                          "use --help for help\n")
425         sys.exit(1)
426     else:
427         extract_vendor_ids(sys.argv[2])
428         extract_ofp_errors(sys.argv[1])