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