123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138 |
- #!/usr/bin/python
- # 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
- # Maximum line width.
- LINE_WIDTH=80
- class Problem(Exception):
- pass
- def commented_line(fmt, argument, maxwidth=LINE_WIDTH):
- """
- Return fmt%argument, for use as a commented line. If the line would
- be longer than maxwidth, truncate argument.
- Requires that fmt%"..." will fit into maxwidth characters.
- """
- result = fmt % argument
- if len(result) <= maxwidth:
- return result
- else:
- # figure out how much we need to truncate by to fit the argument,
- # plus an ellipsis.
- ellipsis = "..."
- result = fmt % (argument + ellipsis)
- overrun = len(result) - maxwidth
- truncated_argument = argument[:-overrun] + ellipsis
- result = fmt % truncated_argument
- assert len(result) <= maxwidth
- return result
- 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):
- f_out.write(commented_line("#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'):
- f_out.write(commented_line("#endif /* %s */\n",
- cur_level[0][1]))
- else:
- f_out.write(commented_line("#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)
|