24fddbbfbe353a1c89c74d3cb927da1a73ccd17e
[cascardo/ovs.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 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 = {"OF":    ("OF1.0", "OF1.1"),
208                               "OF1.0": ("OF1.0",),
209                               "OF1.1": ("OF1.1",),
210                               "NX":    ("OF1.0", "OF1.1"),
211                               "NX1.0": ("OF1.0",),
212                               "NX1.1": ("OF1.1",)}
213                 if targets not in target_map:
214                     fatal("%s: unknown error domain" % targets)
215                 for target in target_map[targets]:
216                     if type_ not in domain[target]:
217                         domain[target][type_] = {}
218                     if code in domain[target][type_]:
219                         fatal("%s: duplicate assignment in domain" % dst)
220                     domain[target][type_][code] = enum
221                     reverse[target][enum] = (type_, code)
222
223         inputFile.close()
224
225     print """\
226 /* Generated automatically; do not modify!     -*- buffer-read-only: t -*- */
227
228 #define OFPERR_N_ERRORS %d
229
230 struct ofperr_domain {
231     const char *name;
232     uint8_t version;
233     enum ofperr (*decode)(uint16_t type, uint16_t code);
234     enum ofperr (*decode_type)(uint16_t type);
235     struct pair errors[OFPERR_N_ERRORS];
236 };
237
238 static const char *error_names[OFPERR_N_ERRORS] = {
239 %s
240 };
241
242 static const char *error_comments[OFPERR_N_ERRORS] = {
243 %s
244 };\
245 """ % (len(names),
246        '\n'.join('    "%s",' % name for name in names),
247        '\n'.join('    "%s",' % re.sub(r'(["\\])', r'\\\1', comment)
248                  for comment in comments))
249
250     def output_domain(map, name, description, version):
251         print """
252 static enum ofperr
253 %s_decode(uint16_t type, uint16_t code)
254 {
255     switch ((type << 16) | code) {""" % name
256         for enum in names:
257             if enum not in map:
258                 continue
259             type_, code = map[enum]
260             if code is None:
261                 continue
262             print "    case (%d << 16) | %d:" % (type_, code)
263             print "        return OFPERR_%s;" % enum
264         print """\
265     }
266
267     return 0;
268 }
269
270 static enum ofperr
271 %s_decode_type(uint16_t type)
272 {
273     switch (type) {""" % name
274         for enum in names:
275             if enum not in map:
276                 continue
277             type_, code = map[enum]
278             if code is not None:
279                 continue
280             print "    case %d:" % type_
281             print "        return OFPERR_%s;" % enum
282         print """\
283     }
284
285     return 0;
286 }"""
287
288         print """
289 const struct ofperr_domain %s = {
290     "%s",
291     %d,
292     %s_decode,
293     %s_decode_type,
294     {""" % (name, description, version, name, name)
295         for enum in names:
296             if enum in map:
297                 type_, code = map[enum]
298                 if code == None:
299                     code = -1
300             else:
301                 type_ = code = -1
302             print "        { %2d, %3d }, /* %s */" % (type_, code, enum)
303         print """\
304     },
305 };"""
306
307     output_domain(reverse["OF1.0"], "ofperr_of10", "OpenFlow 1.0", 0x01)
308     output_domain(reverse["OF1.1"], "ofperr_of11", "OpenFlow 1.1", 0x02)
309
310 if __name__ == '__main__':
311     if '--help' in sys.argv:
312         usage()
313     elif len(sys.argv) < 2:
314         sys.stderr.write("at least one non-option argument required; "
315                          "use --help for help\n")
316         sys.exit(1)
317     else:
318         extract_ofp_errors(sys.argv[1:])