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