32d0913c43b24df9e9cfced9bc91cd3d3c99c9da
[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+)(?:,(\d+))?\)$', dst)
216                 if not m:
217                     fatal("%s: syntax error in destination" % dst)
218                 targets = m.group(1)
219                 type_ = int(m.group(2))
220                 if m.group(3):
221                     code = int(m.group(3))
222                 else:
223                     code = None
224
225                 target_map = {"OF1.0+": ("OF1.0", "OF1.1", "OF1.2", "OF1.3"),
226                               "OF1.1+": ("OF1.1", "OF1.2", "OF1.3"),
227                               "OF1.2+": ("OF1.2", "OF1.3"),
228                               "OF1.3+": ("OF1.3",),
229                               "OF1.0":  ("OF1.0",),
230                               "OF1.1":  ("OF1.1",),
231                               "OF1.2":  ("OF1.2",),
232                               "OF1.3":  ("OF1.3",),
233                               "NX1.0+": ("OF1.0", "OF1.1", "OF1.2", "OF1.3"),
234                               "NX1.1+": ("OF1.1", "OF1.2", "OF1.3"),
235                               "NX1.2+": ("OF1.2", "OF1.3"),
236                               "NX1.3+": ("OF1.3",),
237                               "NX1.0":  ("OF1.0",),
238                               "NX1.1":  ("OF1.1",),
239                               "NX1.2":  ("OF1.2",),
240                               "NX1.3":  ("OF1.3",)}
241                 if targets not in target_map:
242                     fatal("%s: unknown error domain" % targets)
243                 if targets.startswith('NX') and code < 0x100:
244                     fatal("%s: NX domain code cannot be less than 0x100" % dst)
245                 if targets.startswith('OF') and code >= 0x100:
246                     fatal("%s: OF domain code cannot be greater than 0x100"
247                           % dst)
248                 for target in target_map[targets]:
249                     domain[target].setdefault(type_, {})
250                     if code in domain[target][type_]:
251                         msg = "%d,%d in %s means both %s and %s" % (
252                             type_, code, target,
253                             domain[target][type_][code][0], enum)
254                         if msg in expected_errors:
255                             del expected_errors[msg]
256                         else:
257                             error("%s: %s." % (dst, msg))
258                             sys.stderr.write("%s:%d: %s: Here is the location "
259                                              "of the previous definition.\n"
260                                              % (domain[target][type_][code][1],
261                                                 domain[target][type_][code][2],
262                                                 dst))
263                     else:
264                         domain[target][type_][code] = (enum, fileName,
265                                                        lineNumber)
266
267                     if enum in reverse[target]:
268                         error("%s: %s in %s means both %d,%d and %d,%d." %
269                               (dst, enum, target,
270                                reverse[target][enum][0],
271                                reverse[target][enum][1],
272                                type_, code))
273                     reverse[target][enum] = (type_, code)
274
275         inputFile.close()
276
277     for fn, ln in expected_errors.values():
278         sys.stderr.write("%s:%d: expected duplicate not used.\n" % (fn, ln))
279         n_errors += 1
280
281     if n_errors:
282         sys.exit(1)
283
284     print ("""\
285 /* Generated automatically; do not modify!     -*- buffer-read-only: t -*- */
286
287 #define OFPERR_N_ERRORS %d
288
289 struct ofperr_domain {
290     const char *name;
291     uint8_t version;
292     enum ofperr (*decode)(uint16_t type, uint16_t code);
293     struct pair errors[OFPERR_N_ERRORS];
294 };
295
296 static const char *error_names[OFPERR_N_ERRORS] = {
297 %s
298 };
299
300 static const char *error_comments[OFPERR_N_ERRORS] = {
301 %s
302 };\
303 """ % (len(names),
304        '\n'.join('    "%s",' % name for name in names),
305        '\n'.join('    "%s",' % re.sub(r'(["\\])', r'\\\1', comment)
306                  for comment in comments)))
307
308     def output_domain(map, name, description, version):
309         print ("""
310 static enum ofperr
311 %s_decode(uint16_t type, uint16_t code)
312 {
313     switch ((type << 16) | code) {""" % name)
314         found = set()
315         for enum in names:
316             if enum not in map:
317                 continue
318             type_, code = map[enum]
319             if code is None:
320                 continue
321             value = (type_ << 16) | code
322             if value in found:
323                 continue
324             found.add(value)
325             print ("    case (%d << 16) | %d:" % (type_, code))
326             print ("        return OFPERR_%s;" % enum)
327         print ("""\
328     }
329
330     return 0;
331 }""")
332
333         print ("""
334 static const struct ofperr_domain %s = {
335     "%s",
336     %d,
337     %s_decode,
338     {""" % (name, description, version, name))
339         for enum in names:
340             if enum in map:
341                 type_, code = map[enum]
342                 if code == None:
343                     code = -1
344             else:
345                 type_ = code = -1
346             print ("        { %2d, %3d }, /* %s */" % (type_, code, enum))
347         print ("""\
348     },
349 };""")
350
351     output_domain(reverse["OF1.0"], "ofperr_of10", "OpenFlow 1.0", 0x01)
352     output_domain(reverse["OF1.1"], "ofperr_of11", "OpenFlow 1.1", 0x02)
353     output_domain(reverse["OF1.2"], "ofperr_of12", "OpenFlow 1.2", 0x03)
354     output_domain(reverse["OF1.3"], "ofperr_of13", "OpenFlow 1.3", 0x04)
355
356 if __name__ == '__main__':
357     if '--help' in sys.argv:
358         usage()
359     elif len(sys.argv) < 2:
360         sys.stderr.write("at least one non-option argument required; "
361                          "use --help for help\n")
362         sys.exit(1)
363     else:
364         extract_ofp_errors(sys.argv[1:])