summaryrefslogtreecommitdiff
path: root/scripts/maint/annotate_ifdef_directives
blob: 15121652d753783420c56587f9e179e7c55d899f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
#!/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

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):
                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'):
                f_out.write("#endif /* %s */\n"%cur_level[0][1])
            else:
                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)