extract-ofp-errors: Check that error codes are in the expected ranges.
[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", "NX1.0", "NX1.1"):
155         domain[domain_name] = {}
156         reverse[domain_name] = {}
157
158     n_errors = 0
159     expected_errors = {}
160
161     global fileName
162     for fileName in filenames:
163         global inputFile
164         global lineNumber
165         inputFile = open(fileName)
166         lineNumber = 0
167
168         while True:
169             getLine()
170             if re.match('enum ofperr', line):
171                 break
172
173         while True:
174             getLine()
175             if line.startswith('/*') or not line or line.isspace():
176                 continue
177             elif re.match('}', line):
178                 break
179
180             if not line.lstrip().startswith('/*'):
181                 fatal("unexpected syntax between errors")
182
183             comment = line.lstrip()[2:].strip()
184             while not comment.endswith('*/'):
185                 getLine()
186                 if line.startswith('/*') or not line or line.isspace():
187                     fatal("unexpected syntax within error")
188                 comment += ' %s' % line.lstrip('* \t').rstrip(' \t\r\n')
189             comment = comment[:-2].rstrip()
190
191             m = re.match('Expected: (.*)\.$', comment)
192             if m:
193                 expected_errors[m.group(1)] = (fileName, lineNumber)
194                 continue
195
196             m = re.match('((?:.(?!\.  ))+.)\.  (.*)$', comment)
197             if not m:
198                 fatal("unexpected syntax between errors")
199
200             dsts, comment = m.groups()
201
202             getLine()
203             m = re.match('\s+(?:OFPERR_((?:OFP|NX)[A-Z0-9_]+))(\s*=\s*OFPERR_OFS)?,',
204                          line)
205             if not m:
206                 fatal("syntax error expecting enum value")
207
208             enum = m.group(1)
209
210             comments.append(re.sub('\[[^]]*\]', '', comment))
211             names.append(enum)
212
213             for dst in dsts.split(', '):
214                 m = re.match(r'([A-Z0-9.+]+)\((\d+|(0x)[0-9a-fA-F]+)(?:,(\d+))?\)$', dst)
215                 if not m:
216                     fatal("%s: syntax error in destination" % dst)
217                 targets = m.group(1)
218                 if m.group(3):
219                     base = 16
220                 else:
221                     base = 10
222                 type_ = int(m.group(2), base)
223                 if m.group(4):
224                     code = int(m.group(4))
225                 else:
226                     code = None
227
228                 target_map = {"OF1.0+": ("OF1.0", "OF1.1", "OF1.2"),
229                               "OF1.1+": ("OF1.1", "OF1.2"),
230                               "OF1.2+": ("OF1.2",),
231                               "OF1.0":  ("OF1.0",),
232                               "OF1.1":  ("OF1.1",),
233                               "OF1.2":  ("OF1.2",),
234                               "NX1.0+": ("OF1.0", "OF1.1", "OF1.2"),
235                               "NX1.0":  ("OF1.0",),
236                               "NX1.1":  ("OF1.1",),
237                               "NX1.1+": ("OF1.1",),
238                               "NX1.2":  ("OF1.2",)}
239                 if targets not in target_map:
240                     fatal("%s: unknown error domain" % targets)
241                 if targets.startswith('NX') and code < 0x100:
242                     fatal("%s: NX domain code cannot be less than 0x100" % dst)
243                 if targets.startswith('OF') and code >= 0x100:
244                     fatal("%s: OF domain code cannot be greater than 0x100"
245                           % dst)
246                 for target in target_map[targets]:
247                     domain[target].setdefault(type_, {})
248                     if code in domain[target][type_]:
249                         msg = "%d,%d in %s means both %s and %s" % (
250                             type_, code, target,
251                             domain[target][type_][code][0], enum)
252                         if msg in expected_errors:
253                             del expected_errors[msg]
254                         else:
255                             error("%s: %s." % (dst, msg))
256                             sys.stderr.write("%s:%d: %s: Here is the location "
257                                              "of the previous definition.\n"
258                                              % (domain[target][type_][code][1],
259                                                 domain[target][type_][code][2],
260                                                 dst))
261                     else:
262                         domain[target][type_][code] = (enum, fileName,
263                                                        lineNumber)
264
265                     if enum in reverse[target]:
266                         error("%s: %s in %s means both %d,%d and %d,%d." %
267                               (dst, enum, target,
268                                reverse[target][enum][0],
269                                reverse[target][enum][1],
270                                type_, code))
271                     reverse[target][enum] = (type_, code)
272
273         inputFile.close()
274
275     for fn, ln in expected_errors.itervalues():
276         sys.stderr.write("%s:%d: expected duplicate not used.\n" % (fn, ln))
277         n_errors += 1
278
279     if n_errors:
280         sys.exit(1)
281
282     print """\
283 /* Generated automatically; do not modify!     -*- buffer-read-only: t -*- */
284
285 #define OFPERR_N_ERRORS %d
286
287 struct ofperr_domain {
288     const char *name;
289     uint8_t version;
290     enum ofperr (*decode)(uint16_t type, uint16_t code);
291     enum ofperr (*decode_type)(uint16_t type);
292     struct pair errors[OFPERR_N_ERRORS];
293 };
294
295 static const char *error_names[OFPERR_N_ERRORS] = {
296 %s
297 };
298
299 static const char *error_comments[OFPERR_N_ERRORS] = {
300 %s
301 };\
302 """ % (len(names),
303        '\n'.join('    "%s",' % name for name in names),
304        '\n'.join('    "%s",' % re.sub(r'(["\\])', r'\\\1', comment)
305                  for comment in comments))
306
307     def output_domain(map, name, description, version):
308         print """
309 static enum ofperr
310 %s_decode(uint16_t type, uint16_t code)
311 {
312     switch ((type << 16) | code) {""" % name
313         found = set()
314         for enum in names:
315             if enum not in map:
316                 continue
317             type_, code = map[enum]
318             if code is None:
319                 continue
320             value = (type_ << 16) | code
321             if value in found:
322                 continue
323             found.add(value)
324             print "    case (%d << 16) | %d:" % (type_, code)
325             print "        return OFPERR_%s;" % enum
326         print """\
327     }
328
329     return 0;
330 }
331
332 static enum ofperr
333 %s_decode_type(uint16_t type)
334 {
335     switch (type) {""" % name
336         for enum in names:
337             if enum not in map:
338                 continue
339             type_, code = map[enum]
340             if code is not None:
341                 continue
342             print "    case %d:" % type_
343             print "        return OFPERR_%s;" % enum
344         print """\
345     }
346
347     return 0;
348 }"""
349
350         print """
351 static const struct ofperr_domain %s = {
352     "%s",
353     %d,
354     %s_decode,
355     %s_decode_type,
356     {""" % (name, description, version, name, name)
357         for enum in names:
358             if enum in map:
359                 type_, code = map[enum]
360                 if code == None:
361                     code = -1
362             else:
363                 type_ = code = -1
364             print "        { %2d, %3d }, /* %s */" % (type_, code, enum)
365         print """\
366     },
367 };"""
368
369     output_domain(reverse["OF1.0"], "ofperr_of10", "OpenFlow 1.0", 0x01)
370     output_domain(reverse["OF1.1"], "ofperr_of11", "OpenFlow 1.1", 0x02)
371     output_domain(reverse["OF1.2"], "ofperr_of12", "OpenFlow 1.2", 0x03)
372
373 if __name__ == '__main__':
374     if '--help' in sys.argv:
375         usage()
376     elif len(sys.argv) < 2:
377         sys.stderr.write("at least one non-option argument required; "
378                          "use --help for help\n")
379         sys.exit(1)
380     else:
381         extract_ofp_errors(sys.argv[1:])