4b3d46ba2e3b9d9501238438a806c93b30ffbca5
[openvswitch] / 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 def fatal(msg):
70     sys.stderr.write("%s:%d: %s\n" % (fileName, lineNumber, msg))
71     sys.exit(1)
72
73 def skipDirective():
74     getToken()
75     while token != '$':
76         getToken()
77
78 def isId(s):
79     return re.match(idRe + "$", s) != None
80
81 def forceId():
82     if not isId(token):
83         fatal("identifier expected")
84
85 def forceInteger():
86     if not re.match('[0-9]+$', token):
87         fatal("integer expected")
88
89 def match(t):
90     if token == t:
91         getToken()
92         return True
93     else:
94         return False
95
96 def forceMatch(t):
97     if not match(t):
98         fatal("%s expected" % t)
99
100 def parseTaggedName():
101     assert token in ('struct', 'union')
102     name = token
103     getToken()
104     forceId()
105     name = "%s %s" % (name, token)
106     getToken()
107     return name
108
109 def print_enum(tag, constants, storage_class):
110     print """
111 %(storage_class)sconst char *
112 %(tag)s_to_string(uint16_t value)
113 {
114     switch (value) {\
115 """ % {"tag": tag,
116        "bufferlen": len(tag) + 32,
117        "storage_class": storage_class}
118     for constant in constants:
119         print "    case %s: return \"%s\";" % (constant, constant)
120     print """\
121     }
122     return NULL;
123 }\
124 """ % {"tag": tag}
125
126 def usage():
127     argv0 = os.path.basename(sys.argv[0])
128     print '''\
129 %(argv0)s, for extracting OpenFlow error codes from header files
130 usage: %(argv0)s FILE [FILE...]
131
132 This program reads the header files specified on the command line and
133 outputs a C source file for translating OpenFlow error codes into
134 strings, for use as lib/ofp-errors.c in the Open vSwitch source tree.
135
136 This program is specialized for reading lib/ofp-errors.h.  It will not
137 work on arbitrary header files without extensions.\
138 ''' % {"argv0": argv0}
139     sys.exit(0)
140
141 def extract_ofp_errors(filenames):
142     error_types = {}
143
144     comments = []
145     names = []
146     domain = {}
147     reverse = {}
148     for domain_name in ("OF1.0", "OF1.1", "NX1.0", "NX1.1"):
149         domain[domain_name] = {}
150         reverse[domain_name] = {}
151
152     global fileName
153     for fileName in filenames:
154         global inputFile
155         global lineNumber
156         inputFile = open(fileName)
157         lineNumber = 0
158
159         while True:
160             getLine()
161             if re.match('enum ofperr', line):
162                 break
163
164         while True:
165             getLine()
166             if line.startswith('/*') or not line or line.isspace():
167                 continue
168             elif re.match('}', line):
169                 break
170
171             m = re.match('\s+/\* ((?:.(?!\.  ))+.)\.  (.*)$', line)
172             if not m:
173                 fatal("unexpected syntax between errors")
174
175             dsts, comment = m.groups()
176
177             comment.rstrip()
178             while not comment.endswith('*/'):
179                 getLine()
180                 if line.startswith('/*') or not line or line.isspace():
181                     fatal("unexpected syntax within error")
182                 comment += ' %s' % line.lstrip('* \t').rstrip(' \t\r\n')
183             comment = comment[:-2].rstrip()
184
185             getLine()
186             m = re.match('\s+(?:OFPERR_((?:OFP|NX)[A-Z0-9_]+))(\s*=\s*OFPERR_OFS)?,',
187                          line)
188             if not m:
189                 fatal("syntax error expecting enum value")
190
191             enum = m.group(1)
192
193             comments.append(comment)
194             names.append(enum)
195
196             for dst in dsts.split(', '):
197                 m = re.match(r'([A-Z0-9.+]+)\((\d+)(?:,(\d+))?\)$', dst)
198                 if not m:
199                     fatal("%s: syntax error in destination" % dst)
200                 targets = m.group(1)
201                 type_ = int(m.group(2))
202                 if m.group(3):
203                     code = int(m.group(3))
204                 else:
205                     code = None
206
207                 target_map = {"OF1.0+": ("OF1.0", "OF1.1"),
208                               "OF1.1+": ("OF1.1",),
209                               "OF1.0":  ("OF1.0",),
210                               "OF1.1":  ("OF1.1",),
211                               "NX1.0+": ("OF1.0", "OF1.1"),
212                               "NX1.0":  ("OF1.0",),
213                               "NX1.1":  ("OF1.1",)}
214                 if targets not in target_map:
215                     fatal("%s: unknown error domain" % targets)
216                 for target in target_map[targets]:
217                     if type_ not in domain[target]:
218                         domain[target][type_] = {}
219                     if code in domain[target][type_]:
220                         fatal("%s: duplicate assignment in domain" % dst)
221                     domain[target][type_][code] = enum
222                     reverse[target][enum] = (type_, code)
223
224         inputFile.close()
225
226     print """\
227 /* Generated automatically; do not modify!     -*- buffer-read-only: t -*- */
228
229 #define OFPERR_N_ERRORS %d
230
231 struct ofperr_domain {
232     const char *name;
233     uint8_t version;
234     enum ofperr (*decode)(uint16_t type, uint16_t code);
235     enum ofperr (*decode_type)(uint16_t type);
236     struct pair errors[OFPERR_N_ERRORS];
237 };
238
239 static const char *error_names[OFPERR_N_ERRORS] = {
240 %s
241 };
242
243 static const char *error_comments[OFPERR_N_ERRORS] = {
244 %s
245 };\
246 """ % (len(names),
247        '\n'.join('    "%s",' % name for name in names),
248        '\n'.join('    "%s",' % re.sub(r'(["\\])', r'\\\1', comment)
249                  for comment in comments))
250
251     def output_domain(map, name, description, version):
252         print """
253 static enum ofperr
254 %s_decode(uint16_t type, uint16_t code)
255 {
256     switch ((type << 16) | code) {""" % name
257         for enum in names:
258             if enum not in map:
259                 continue
260             type_, code = map[enum]
261             if code is None:
262                 continue
263             print "    case (%d << 16) | %d:" % (type_, code)
264             print "        return OFPERR_%s;" % enum
265         print """\
266     }
267
268     return 0;
269 }
270
271 static enum ofperr
272 %s_decode_type(uint16_t type)
273 {
274     switch (type) {""" % name
275         for enum in names:
276             if enum not in map:
277                 continue
278             type_, code = map[enum]
279             if code is not None:
280                 continue
281             print "    case %d:" % type_
282             print "        return OFPERR_%s;" % enum
283         print """\
284     }
285
286     return 0;
287 }"""
288
289         print """
290 const struct ofperr_domain %s = {
291     "%s",
292     %d,
293     %s_decode,
294     %s_decode_type,
295     {""" % (name, description, version, name, name)
296         for enum in names:
297             if enum in map:
298                 type_, code = map[enum]
299                 if code == None:
300                     code = -1
301             else:
302                 type_ = code = -1
303             print "        { %2d, %3d }, /* %s */" % (type_, code, enum)
304         print """\
305     },
306 };"""
307
308     output_domain(reverse["OF1.0"], "ofperr_of10", "OpenFlow 1.0", 0x01)
309     output_domain(reverse["OF1.1"], "ofperr_of11", "OpenFlow 1.1", 0x02)
310
311 if __name__ == '__main__':
312     if '--help' in sys.argv:
313         usage()
314     elif len(sys.argv) < 2:
315         sys.stderr.write("at least one non-option argument required; "
316                          "use --help for help\n")
317         sys.exit(1)
318     else:
319         extract_ofp_errors(sys.argv[1:])