tor/scripts/maint/annotate_ifdef_directives

111 lines
3.8 KiB
Plaintext
Raw Normal View History

#!/usr/bin/python
2019-01-16 18:33:22 +01:00
# Copyright (c) 2017-2019, The Tor Project, Inc.
# See LICENSE for licensing information
# This script iterates over a list of C files. For each file, it looks at the
# #if/#else C macros, and annotates them with comments explaining what they
# match.
#
# For example, it replaces this:
#
# #ifdef HAVE_OCELOT
# // 500 lines of ocelot code
# #endif
#
# with this:
#
# #ifdef HAVE_OCELOT
# // 500 lines of ocelot code
# #endif /* defined(HAVE_OCELOT) */
#
# Note that only #else and #endif lines are annotated. Existing comments
# on those lines are removed.
import re
# Any block with fewer than this many lines does not need annotations.
LINE_OBVIOUSNESS_LIMIT = 4
class Problem(Exception):
pass
def uncomment(s):
"""
Remove existing trailing comments from an #else or #endif line.
"""
s = re.sub(r'//.*','',s)
s = re.sub(r'/\*.*','',s)
return s.strip()
def translate(f_in, f_out):
"""
Read a file from f_in, and write its annotated version to f_out.
"""
# A stack listing our current if/else state. Each member of the stack
# is a list of directives. Each directive is a 3-tuple of
# (command, rest, lineno)
# where "command" is one of if/ifdef/ifndef/else/elif, and where
# "rest" is an expression in a format suitable for use with #if, and where
# lineno is the line number where the directive occurred.
stack = []
# the stack element corresponding to the top level of the file.
whole_file = []
cur_level = whole_file
lineno = 0
for line in f_in:
lineno += 1
m = re.match(r'\s*#\s*(if|ifdef|ifndef|else|endif|elif)\b\s*(.*)',
line)
if not m:
# no directive, so we can just write it out.
f_out.write(line)
continue
command,rest = m.groups()
if command in ("if", "ifdef", "ifndef"):
# The #if directive pushes us one level lower on the stack.
if command == 'ifdef':
rest = "defined(%s)"%uncomment(rest)
elif command == 'ifndef':
rest = "!defined(%s)"%uncomment(rest)
elif rest.endswith("\\"):
rest = rest[:-1]+"..."
rest = uncomment(rest)
new_level = [ (command, rest, lineno) ]
stack.append(cur_level)
cur_level = new_level
f_out.write(line)
elif command in ("else", "elif"):
# We stay at the same level on the stack. If we have an #else,
# we comment it.
if len(cur_level) == 0 or cur_level[-1][0] == 'else':
raise Problem("Unexpected #%s on %d"% (command,lineno))
if (len(cur_level) == 1 and command == 'else' and
lineno > cur_level[0][2] + LINE_OBVIOUSNESS_LIMIT):
2017-09-15 22:11:48 +02:00
f_out.write("#else /* !(%s) */\n"%cur_level[0][1])
else:
f_out.write(line)
cur_level.append((command, rest, lineno))
else:
# We pop one element on the stack, and comment an endif.
assert command == 'endif'
if len(stack) == 0:
raise Problem("Unmatched #%s on %s"% (command,lineno))
if lineno <= cur_level[0][2] + LINE_OBVIOUSNESS_LIMIT:
f_out.write(line)
elif len(cur_level) == 1 or (
len(cur_level) == 2 and cur_level[1][0] == 'else'):
2017-09-15 22:11:48 +02:00
f_out.write("#endif /* %s */\n"%cur_level[0][1])
else:
2017-09-15 22:11:48 +02:00
f_out.write("#endif /* %s || ... */\n"%cur_level[0][1])
cur_level = stack.pop()
if len(stack) or cur_level != whole_file:
raise Problem("Missing #endif")
import sys,os
for fn in sys.argv[1:]:
with open(fn+"_OUT", 'w') as output_file:
translate(open(fn, 'r'), output_file)
os.rename(fn+"_OUT", fn)