vtkindent.py 9.67 KB
Newer Older
1 2
#!/usr/bin/env python
"""
3 4 5
This script takes old-style "Whitesmiths" indented VTK source files as
input, and re-indents the braces according to the new VTK style.
Only the brace indentation is modified.
6 7 8 9 10 11 12 13

Written by David Gobbi on Sep 30, 2015.
"""

import sys
import re

def reindent(filename):
14
    """Reindent a file from Whitesmiths style to Allman style"""
15 16 17 18

    # This first part of this function clears all strings and comments
    # where non-grammatical braces might be hiding.

David Gobbi's avatar
David Gobbi committed
19 20 21 22 23 24 25 26 27 28 29
    keychar = re.compile(r"""[/"']""")
    c_comment = re.compile(r"\/\*(\*(?!\/)|[^*])*\*\/")
    c_comment_start = re.compile(r"\/\*(\*(?!\/)|[^*])*$")
    c_comment_end = re.compile(r"^(\*(?!\/)|[^*])*\*\/")
    cpp_comment = re.compile(r"\/\/.*")
    string_literal = re.compile(r'"([^\\"]|\\.)*"')
    string_literal_start = re.compile(r'"([^\\"]|\\.)*\\$')
    string_literal_end = re.compile(r'^([^\\"]|\\.)*"')
    char_literal = re.compile(r"'([^\\']|\\.)*'")
    char_literal_start = re.compile(r"'([^\\']|\\.)*\\$")
    char_literal_end = re.compile(r"^([^\\']|\\.)*'")
30

31 32 33 34 35 36 37 38 39
    try:
        f = open(filename)
        lines = f.readlines()
        f.close()
    except:
        sys.stderr.write(filename + ": ")
        sys.stderr.write(str(sys.exc_info()[1]) + "\n")
        sys.exit(1)

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
    n = len(lines)
    newlines = []

    cont = None

    for i in range(n):
        line = lines[i].rstrip()
        if cont is not None:
            match = cont.match(line)
            if match:
                line = line[match.end():]
                cont = None
            else:
                if cont is c_comment_end:
                    newlines.append("")
                    continue
                else:
                    newlines.append('\\')
                    continue

        pos = 0
        while True:
            match = keychar.search(line, pos)
            if match is None:
                break
            pos = match.start()
            end = match.end()
            match = c_comment.match(line, pos)
            if match:
                line = line[0:pos] + " " + line[match.end():]
                pos += 1
                continue
            match = c_comment_start.match(line, pos)
            if match:
                if line[-1] == '\\':
                    line = line[0:pos] + ' \\'
                else:
                    line = line[0:pos]
                cont = c_comment_end
                break
            match = cpp_comment.match(line, pos)
            if match:
                if line[-1] == '\\':
                    line = line[0:pos] + ' \\'
                else:
                    line = line[0:pos]
                break
            match = string_literal.match(line, pos)
            if match:
                line = line[0:pos] + "\"\"" + line[match.end():]
                pos += 2
                continue
            match = string_literal_start.match(line, pos)
            if match:
                line = line[0:pos] + "\"\"\\"
                cont = string_literal_end
                break
            match = char_literal.match(line, pos)
            if match:
                line = line[0:pos] + "\' \'" + line[match.end():]
                pos += 3
                continue
            match = char_literal_start.match(line, pos)
            if match:
                line = line[0:pos] + "\' \'\\"
                cont = char_literal_end
                break
            pos += 1

        newlines.append(line.rstrip())

111 112 113 114 115 116 117
    # all changes go through this function
    lines_changed = {}
    def changeline(i, newtext, lines_changed=lines_changed):
         if newtext != lines[i]:
              lines[i] = newtext
              lines_changed[i] = newtext

118 119 120 121 122 123 124 125 126 127 128 129 130
    # Use a stack to keep track of braces and, whenever a closing brace is
    # found, properly indent it and its opening brace.
    # For #if directives, check whether there are mismatched braces within
    # the conditional block, and if so, print a warning and reset the stack
    # to the depth that it had at the start of the block.

    # stack holds tuples (delim, row, col, newcol)
    stack = []
    lastdepth = 0

    # save the stack for conditional compilation blocks
    dstack = []

David Gobbi's avatar
David Gobbi committed
131 132 133 134 135
    directive = re.compile(r" *# *(..)")
    label = re.compile(r""" *(case  *)?(' '|""|[A-Za-z0-9_]| *:: *)+ *:$""")
    delims = re.compile(r"[{}()\[\]]")
    spaces = re.compile(r" *")
    cplusplus = re.compile(r" *# *ifdef  *__cplusplus")
136 137 138 139 140 141 142

    lastpos = 0
    newpos = 0
    continuation = False
    new_context = True
    in_else = False
    in_define = False
143
    in_assign = False
144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195
    leaving_define = False
    save_stack = None

    for i in range(n):
        line = newlines[i]
        pos = 0

        # restore stack when leaving #define
        if leaving_define:
            stack, lastpos, newpos, continuation = save_stack
            save_stack = None
            in_define = False
            leaving_define = False

        # handle #if conditionals
        is_directive = False
        in_else = False
        match = directive.match(line)
        if match:
            is_directive = True
            if match.groups()[0] == 'if':
                dstack.append((list(stack), line))
            elif match.groups()[0] in ('en', 'el'):
                oldstack, dline = dstack.pop()
                if len(stack) > len(oldstack) and not cplusplus.match(dline):
                    sys.stderr.write(filename + ":" + str(i) + ": ")
                    sys.stderr.write("mismatched delimiter in \"" +
                                     dline + "\" block\n")
                if match.groups()[0] == 'el':
                    in_else = True
                    stack = oldstack
                    dstack.append((list(stack), line))
            elif match.groups()[0] == 'de':
                in_define = True
                leaving_define = False
                save_stack = (stack, lastpos, newpos, continuation)
                stack = []
                new_context = True

        # remove backslash at end of line, if present
        if len(line) > 0 and line[-1] == '\\':
            line = line[0:-1].rstrip()
        elif in_define:
            leaving_define = True

        if not is_directive and len(line) > 0 and not continuation:
            # what is the indentation of the current line?
            match = spaces.match(line)
            newpos = match.end()

        # all statements end with ':', ';', '{', or '}'
        if len(line) > 0:
196 197
            if (new_context or line[-1] in ('{', '}', ';') or
                (label.match(line) and not continuation)):
198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213
                continuation = False
                new_context = False
            elif not is_directive:
                continuation = True

        # search for braces
        while True:
            match = delims.search(line, pos)
            if match is None:
                break
            pos = match.start()
            delim = line[pos]
            if delim in ('(', '['):
                # save delim, row, col, and current indentation
                stack.append((delim, i, pos, newpos))
            elif delim == '{':
214 215 216 217
                if in_assign:
                    # do not adjust braces for initializer lists
                    stack.append((delim, i, pos, pos))
                elif ((in_else or in_define) and spaces.sub("", line) == "{"):
218 219
                    # for opening braces that might have no match
                    indent = " "*lastpos
220
                    changeline(i, spaces.sub(indent, lines[i], count=1))
221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240
                    stack.append((delim, i, lastpos, lastpos))
                else:
                    # save delim, row, col, and previous indentation
                    stack.append((delim, i, pos, lastpos))
                newpos = pos + 2
                lastpos = newpos
            else:
                # found a ')', ']', or '}' delimiter, so pop its partner
                try:
                    ldelim, j, k, newpos = stack.pop()
                except IndexError:
                    ldelim = ""
                if ldelim != {'}':'{', ')':'(', ']':'['}[delim]:
                    sys.stderr.write(filename + ":" + str(i) + ": ")
                    sys.stderr.write("mismatched \'" + delim + "\'\n")
                # adjust the indentation of matching '{', '}'
                if (ldelim == '{' and delim == '}' and
                    spaces.sub("", lines[i][0:pos]) == "" and
                    spaces.sub("", lines[j][0:k]) == ""):
                    indent = " "*newpos
241 242
                    changeline(i, spaces.sub(indent, lines[i], count=1))
                    changeline(j, spaces.sub(indent, lines[j], count=1))
243 244
            pos += 1

245 246 247 248 249 250 251 252 253
        # check for " = " and #define assignments for the sake of
        # the { inializer list } that might be on the following line
        if len(line) > 0:
            if (line[-1] == '=' or
                (is_directive and in_define and not leaving_define)):
                in_assign = True
            elif not is_directive:
                in_assign = False

254 255 256 257 258 259 260 261 262 263 264
        lastpos = newpos

    if len(dstack) != 0:
        sys.stderr.write(filename + ": ")
        sys.stderr.write("mismatched #if conditional.\n")

    if len(stack) != 0:
        sys.stderr.write(filename + ":" + str(stack[0][1]) + ": ")
        sys.stderr.write("no match for " + stack[0][0] +
                         " before end of file.\n")

265 266 267 268 269 270 271 272 273 274 275
    if lines_changed:
        # remove any trailing whitespace
        trailing = re.compile(r" *$")
        for i in range(n):
            lines[i] = trailing.sub("", lines[i])
        while n > 0 and lines[n-1].rstrip() == "":
            n -= 1
        # rewrite the file
        ofile = open(filename, 'w')
        ofile.writelines(lines)
        ofile.close()
276 277 278 279 280 281


if __name__ == "__main__":

    for filename in sys.argv[1:]:
         reindent(filename)