17db96d56Sopenharmony_ci#! /usr/bin/env python3
27db96d56Sopenharmony_ci
37db96d56Sopenharmony_ci# Perform massive identifier substitution on C source files.
47db96d56Sopenharmony_ci# This actually tokenizes the files (to some extent) so it can
57db96d56Sopenharmony_ci# avoid making substitutions inside strings or comments.
67db96d56Sopenharmony_ci# Inside strings, substitutions are never made; inside comments,
77db96d56Sopenharmony_ci# it is a user option (off by default).
87db96d56Sopenharmony_ci#
97db96d56Sopenharmony_ci# The substitutions are read from one or more files whose lines,
107db96d56Sopenharmony_ci# when not empty, after stripping comments starting with #,
117db96d56Sopenharmony_ci# must contain exactly two words separated by whitespace: the
127db96d56Sopenharmony_ci# old identifier and its replacement.
137db96d56Sopenharmony_ci#
147db96d56Sopenharmony_ci# The option -r reverses the sense of the substitutions (this may be
157db96d56Sopenharmony_ci# useful to undo a particular substitution).
167db96d56Sopenharmony_ci#
177db96d56Sopenharmony_ci# If the old identifier is prefixed with a '*' (with no intervening
187db96d56Sopenharmony_ci# whitespace), then it will not be substituted inside comments.
197db96d56Sopenharmony_ci#
207db96d56Sopenharmony_ci# Command line arguments are files or directories to be processed.
217db96d56Sopenharmony_ci# Directories are searched recursively for files whose name looks
227db96d56Sopenharmony_ci# like a C file (ends in .h or .c).  The special filename '-' means
237db96d56Sopenharmony_ci# operate in filter mode: read stdin, write stdout.
247db96d56Sopenharmony_ci#
257db96d56Sopenharmony_ci# Symbolic links are always ignored (except as explicit directory
267db96d56Sopenharmony_ci# arguments).
277db96d56Sopenharmony_ci#
287db96d56Sopenharmony_ci# The original files are kept as back-up with a "~" suffix.
297db96d56Sopenharmony_ci#
307db96d56Sopenharmony_ci# Changes made are reported to stdout in a diff-like format.
317db96d56Sopenharmony_ci#
327db96d56Sopenharmony_ci# NB: by changing only the function fixline() you can turn this
337db96d56Sopenharmony_ci# into a program for different changes to C source files; by
347db96d56Sopenharmony_ci# changing the function wanted() you can make a different selection of
357db96d56Sopenharmony_ci# files.
367db96d56Sopenharmony_ci
377db96d56Sopenharmony_ciimport sys
387db96d56Sopenharmony_ciimport re
397db96d56Sopenharmony_ciimport os
407db96d56Sopenharmony_cifrom stat import *
417db96d56Sopenharmony_ciimport getopt
427db96d56Sopenharmony_ci
437db96d56Sopenharmony_cierr = sys.stderr.write
447db96d56Sopenharmony_cidbg = err
457db96d56Sopenharmony_cirep = sys.stdout.write
467db96d56Sopenharmony_ci
477db96d56Sopenharmony_cidef usage():
487db96d56Sopenharmony_ci    progname = sys.argv[0]
497db96d56Sopenharmony_ci    err('Usage: ' + progname +
507db96d56Sopenharmony_ci              ' [-c] [-r] [-s file] ... file-or-directory ...\n')
517db96d56Sopenharmony_ci    err('\n')
527db96d56Sopenharmony_ci    err('-c           : substitute inside comments\n')
537db96d56Sopenharmony_ci    err('-r           : reverse direction for following -s options\n')
547db96d56Sopenharmony_ci    err('-s substfile : add a file of substitutions\n')
557db96d56Sopenharmony_ci    err('\n')
567db96d56Sopenharmony_ci    err('Each non-empty non-comment line in a substitution file must\n')
577db96d56Sopenharmony_ci    err('contain exactly two words: an identifier and its replacement.\n')
587db96d56Sopenharmony_ci    err('Comments start with a # character and end at end of line.\n')
597db96d56Sopenharmony_ci    err('If an identifier is preceded with a *, it is not substituted\n')
607db96d56Sopenharmony_ci    err('inside a comment even when -c is specified.\n')
617db96d56Sopenharmony_ci
627db96d56Sopenharmony_cidef main():
637db96d56Sopenharmony_ci    try:
647db96d56Sopenharmony_ci        opts, args = getopt.getopt(sys.argv[1:], 'crs:')
657db96d56Sopenharmony_ci    except getopt.error as msg:
667db96d56Sopenharmony_ci        err('Options error: ' + str(msg) + '\n')
677db96d56Sopenharmony_ci        usage()
687db96d56Sopenharmony_ci        sys.exit(2)
697db96d56Sopenharmony_ci    bad = 0
707db96d56Sopenharmony_ci    if not args: # No arguments
717db96d56Sopenharmony_ci        usage()
727db96d56Sopenharmony_ci        sys.exit(2)
737db96d56Sopenharmony_ci    for opt, arg in opts:
747db96d56Sopenharmony_ci        if opt == '-c':
757db96d56Sopenharmony_ci            setdocomments()
767db96d56Sopenharmony_ci        if opt == '-r':
777db96d56Sopenharmony_ci            setreverse()
787db96d56Sopenharmony_ci        if opt == '-s':
797db96d56Sopenharmony_ci            addsubst(arg)
807db96d56Sopenharmony_ci    for arg in args:
817db96d56Sopenharmony_ci        if os.path.isdir(arg):
827db96d56Sopenharmony_ci            if recursedown(arg): bad = 1
837db96d56Sopenharmony_ci        elif os.path.islink(arg):
847db96d56Sopenharmony_ci            err(arg + ': will not process symbolic links\n')
857db96d56Sopenharmony_ci            bad = 1
867db96d56Sopenharmony_ci        else:
877db96d56Sopenharmony_ci            if fix(arg): bad = 1
887db96d56Sopenharmony_ci    sys.exit(bad)
897db96d56Sopenharmony_ci
907db96d56Sopenharmony_ci# Change this regular expression to select a different set of files
917db96d56Sopenharmony_ciWanted = r'^[a-zA-Z0-9_]+\.[ch]$'
927db96d56Sopenharmony_cidef wanted(name):
937db96d56Sopenharmony_ci    return re.match(Wanted, name)
947db96d56Sopenharmony_ci
957db96d56Sopenharmony_cidef recursedown(dirname):
967db96d56Sopenharmony_ci    dbg('recursedown(%r)\n' % (dirname,))
977db96d56Sopenharmony_ci    bad = 0
987db96d56Sopenharmony_ci    try:
997db96d56Sopenharmony_ci        names = os.listdir(dirname)
1007db96d56Sopenharmony_ci    except OSError as msg:
1017db96d56Sopenharmony_ci        err(dirname + ': cannot list directory: ' + str(msg) + '\n')
1027db96d56Sopenharmony_ci        return 1
1037db96d56Sopenharmony_ci    names.sort()
1047db96d56Sopenharmony_ci    subdirs = []
1057db96d56Sopenharmony_ci    for name in names:
1067db96d56Sopenharmony_ci        if name in (os.curdir, os.pardir): continue
1077db96d56Sopenharmony_ci        fullname = os.path.join(dirname, name)
1087db96d56Sopenharmony_ci        if os.path.islink(fullname): pass
1097db96d56Sopenharmony_ci        elif os.path.isdir(fullname):
1107db96d56Sopenharmony_ci            subdirs.append(fullname)
1117db96d56Sopenharmony_ci        elif wanted(name):
1127db96d56Sopenharmony_ci            if fix(fullname): bad = 1
1137db96d56Sopenharmony_ci    for fullname in subdirs:
1147db96d56Sopenharmony_ci        if recursedown(fullname): bad = 1
1157db96d56Sopenharmony_ci    return bad
1167db96d56Sopenharmony_ci
1177db96d56Sopenharmony_cidef fix(filename):
1187db96d56Sopenharmony_ci##  dbg('fix(%r)\n' % (filename,))
1197db96d56Sopenharmony_ci    if filename == '-':
1207db96d56Sopenharmony_ci        # Filter mode
1217db96d56Sopenharmony_ci        f = sys.stdin
1227db96d56Sopenharmony_ci        g = sys.stdout
1237db96d56Sopenharmony_ci    else:
1247db96d56Sopenharmony_ci        # File replacement mode
1257db96d56Sopenharmony_ci        try:
1267db96d56Sopenharmony_ci            f = open(filename, 'r')
1277db96d56Sopenharmony_ci        except IOError as msg:
1287db96d56Sopenharmony_ci            err(filename + ': cannot open: ' + str(msg) + '\n')
1297db96d56Sopenharmony_ci            return 1
1307db96d56Sopenharmony_ci        head, tail = os.path.split(filename)
1317db96d56Sopenharmony_ci        tempname = os.path.join(head, '@' + tail)
1327db96d56Sopenharmony_ci        g = None
1337db96d56Sopenharmony_ci    # If we find a match, we rewind the file and start over but
1347db96d56Sopenharmony_ci    # now copy everything to a temp file.
1357db96d56Sopenharmony_ci    lineno = 0
1367db96d56Sopenharmony_ci    initfixline()
1377db96d56Sopenharmony_ci    while 1:
1387db96d56Sopenharmony_ci        line = f.readline()
1397db96d56Sopenharmony_ci        if not line: break
1407db96d56Sopenharmony_ci        lineno = lineno + 1
1417db96d56Sopenharmony_ci        while line[-2:] == '\\\n':
1427db96d56Sopenharmony_ci            nextline = f.readline()
1437db96d56Sopenharmony_ci            if not nextline: break
1447db96d56Sopenharmony_ci            line = line + nextline
1457db96d56Sopenharmony_ci            lineno = lineno + 1
1467db96d56Sopenharmony_ci        newline = fixline(line)
1477db96d56Sopenharmony_ci        if newline != line:
1487db96d56Sopenharmony_ci            if g is None:
1497db96d56Sopenharmony_ci                try:
1507db96d56Sopenharmony_ci                    g = open(tempname, 'w')
1517db96d56Sopenharmony_ci                except IOError as msg:
1527db96d56Sopenharmony_ci                    f.close()
1537db96d56Sopenharmony_ci                    err(tempname+': cannot create: '+
1547db96d56Sopenharmony_ci                        str(msg)+'\n')
1557db96d56Sopenharmony_ci                    return 1
1567db96d56Sopenharmony_ci                f.seek(0)
1577db96d56Sopenharmony_ci                lineno = 0
1587db96d56Sopenharmony_ci                initfixline()
1597db96d56Sopenharmony_ci                rep(filename + ':\n')
1607db96d56Sopenharmony_ci                continue # restart from the beginning
1617db96d56Sopenharmony_ci            rep(repr(lineno) + '\n')
1627db96d56Sopenharmony_ci            rep('< ' + line)
1637db96d56Sopenharmony_ci            rep('> ' + newline)
1647db96d56Sopenharmony_ci        if g is not None:
1657db96d56Sopenharmony_ci            g.write(newline)
1667db96d56Sopenharmony_ci
1677db96d56Sopenharmony_ci    # End of file
1687db96d56Sopenharmony_ci    if filename == '-': return 0 # Done in filter mode
1697db96d56Sopenharmony_ci    f.close()
1707db96d56Sopenharmony_ci    if not g: return 0 # No changes
1717db96d56Sopenharmony_ci    g.close()
1727db96d56Sopenharmony_ci
1737db96d56Sopenharmony_ci    # Finishing touch -- move files
1747db96d56Sopenharmony_ci
1757db96d56Sopenharmony_ci    # First copy the file's mode to the temp file
1767db96d56Sopenharmony_ci    try:
1777db96d56Sopenharmony_ci        statbuf = os.stat(filename)
1787db96d56Sopenharmony_ci        os.chmod(tempname, statbuf[ST_MODE] & 0o7777)
1797db96d56Sopenharmony_ci    except OSError as msg:
1807db96d56Sopenharmony_ci        err(tempname + ': warning: chmod failed (' + str(msg) + ')\n')
1817db96d56Sopenharmony_ci    # Then make a backup of the original file as filename~
1827db96d56Sopenharmony_ci    try:
1837db96d56Sopenharmony_ci        os.rename(filename, filename + '~')
1847db96d56Sopenharmony_ci    except OSError as msg:
1857db96d56Sopenharmony_ci        err(filename + ': warning: backup failed (' + str(msg) + ')\n')
1867db96d56Sopenharmony_ci    # Now move the temp file to the original file
1877db96d56Sopenharmony_ci    try:
1887db96d56Sopenharmony_ci        os.rename(tempname, filename)
1897db96d56Sopenharmony_ci    except OSError as msg:
1907db96d56Sopenharmony_ci        err(filename + ': rename failed (' + str(msg) + ')\n')
1917db96d56Sopenharmony_ci        return 1
1927db96d56Sopenharmony_ci    # Return success
1937db96d56Sopenharmony_ci    return 0
1947db96d56Sopenharmony_ci
1957db96d56Sopenharmony_ci# Tokenizing ANSI C (partly)
1967db96d56Sopenharmony_ci
1977db96d56Sopenharmony_ciIdentifier = '(struct )?[a-zA-Z_][a-zA-Z0-9_]+'
1987db96d56Sopenharmony_ciString = r'"([^\n\\"]|\\.)*"'
1997db96d56Sopenharmony_ciChar = r"'([^\n\\']|\\.)*'"
2007db96d56Sopenharmony_ciCommentStart = r'/\*'
2017db96d56Sopenharmony_ciCommentEnd = r'\*/'
2027db96d56Sopenharmony_ci
2037db96d56Sopenharmony_ciHexnumber = '0[xX][0-9a-fA-F]*[uUlL]*'
2047db96d56Sopenharmony_ciOctnumber = '0[0-7]*[uUlL]*'
2057db96d56Sopenharmony_ciDecnumber = '[1-9][0-9]*[uUlL]*'
2067db96d56Sopenharmony_ciIntnumber = Hexnumber + '|' + Octnumber + '|' + Decnumber
2077db96d56Sopenharmony_ciExponent = '[eE][-+]?[0-9]+'
2087db96d56Sopenharmony_ciPointfloat = r'([0-9]+\.[0-9]*|\.[0-9]+)(' + Exponent + r')?'
2097db96d56Sopenharmony_ciExpfloat = '[0-9]+' + Exponent
2107db96d56Sopenharmony_ciFloatnumber = Pointfloat + '|' + Expfloat
2117db96d56Sopenharmony_ciNumber = Floatnumber + '|' + Intnumber
2127db96d56Sopenharmony_ci
2137db96d56Sopenharmony_ci# Anything else is an operator -- don't list this explicitly because of '/*'
2147db96d56Sopenharmony_ci
2157db96d56Sopenharmony_ciOutsideComment = (Identifier, Number, String, Char, CommentStart)
2167db96d56Sopenharmony_ciOutsideCommentPattern = '(' + '|'.join(OutsideComment) + ')'
2177db96d56Sopenharmony_ciOutsideCommentProgram = re.compile(OutsideCommentPattern)
2187db96d56Sopenharmony_ci
2197db96d56Sopenharmony_ciInsideComment = (Identifier, Number, CommentEnd)
2207db96d56Sopenharmony_ciInsideCommentPattern = '(' + '|'.join(InsideComment) + ')'
2217db96d56Sopenharmony_ciInsideCommentProgram = re.compile(InsideCommentPattern)
2227db96d56Sopenharmony_ci
2237db96d56Sopenharmony_cidef initfixline():
2247db96d56Sopenharmony_ci    global Program
2257db96d56Sopenharmony_ci    Program = OutsideCommentProgram
2267db96d56Sopenharmony_ci
2277db96d56Sopenharmony_cidef fixline(line):
2287db96d56Sopenharmony_ci    global Program
2297db96d56Sopenharmony_ci##  print('-->', repr(line))
2307db96d56Sopenharmony_ci    i = 0
2317db96d56Sopenharmony_ci    while i < len(line):
2327db96d56Sopenharmony_ci        match = Program.search(line, i)
2337db96d56Sopenharmony_ci        if match is None: break
2347db96d56Sopenharmony_ci        i = match.start()
2357db96d56Sopenharmony_ci        found = match.group(0)
2367db96d56Sopenharmony_ci##      if Program is InsideCommentProgram: print(end='... ')
2377db96d56Sopenharmony_ci##      else: print(end='    ')
2387db96d56Sopenharmony_ci##      print(found)
2397db96d56Sopenharmony_ci        if len(found) == 2:
2407db96d56Sopenharmony_ci            if found == '/*':
2417db96d56Sopenharmony_ci                Program = InsideCommentProgram
2427db96d56Sopenharmony_ci            elif found == '*/':
2437db96d56Sopenharmony_ci                Program = OutsideCommentProgram
2447db96d56Sopenharmony_ci        n = len(found)
2457db96d56Sopenharmony_ci        if found in Dict:
2467db96d56Sopenharmony_ci            subst = Dict[found]
2477db96d56Sopenharmony_ci            if Program is InsideCommentProgram:
2487db96d56Sopenharmony_ci                if not Docomments:
2497db96d56Sopenharmony_ci                    print('Found in comment:', found)
2507db96d56Sopenharmony_ci                    i = i + n
2517db96d56Sopenharmony_ci                    continue
2527db96d56Sopenharmony_ci                if found in NotInComment:
2537db96d56Sopenharmony_ci##                  print(end='Ignored in comment: ')
2547db96d56Sopenharmony_ci##                  print(found, '-->', subst)
2557db96d56Sopenharmony_ci##                  print('Line:', line, end='')
2567db96d56Sopenharmony_ci                    subst = found
2577db96d56Sopenharmony_ci##              else:
2587db96d56Sopenharmony_ci##                  print(end='Substituting in comment: ')
2597db96d56Sopenharmony_ci##                  print(found, '-->', subst)
2607db96d56Sopenharmony_ci##                  print('Line:', line, end='')
2617db96d56Sopenharmony_ci            line = line[:i] + subst + line[i+n:]
2627db96d56Sopenharmony_ci            n = len(subst)
2637db96d56Sopenharmony_ci        i = i + n
2647db96d56Sopenharmony_ci    return line
2657db96d56Sopenharmony_ci
2667db96d56Sopenharmony_ciDocomments = 0
2677db96d56Sopenharmony_cidef setdocomments():
2687db96d56Sopenharmony_ci    global Docomments
2697db96d56Sopenharmony_ci    Docomments = 1
2707db96d56Sopenharmony_ci
2717db96d56Sopenharmony_ciReverse = 0
2727db96d56Sopenharmony_cidef setreverse():
2737db96d56Sopenharmony_ci    global Reverse
2747db96d56Sopenharmony_ci    Reverse = (not Reverse)
2757db96d56Sopenharmony_ci
2767db96d56Sopenharmony_ciDict = {}
2777db96d56Sopenharmony_ciNotInComment = {}
2787db96d56Sopenharmony_cidef addsubst(substfile):
2797db96d56Sopenharmony_ci    try:
2807db96d56Sopenharmony_ci        fp = open(substfile, 'r')
2817db96d56Sopenharmony_ci    except IOError as msg:
2827db96d56Sopenharmony_ci        err(substfile + ': cannot read substfile: ' + str(msg) + '\n')
2837db96d56Sopenharmony_ci        sys.exit(1)
2847db96d56Sopenharmony_ci    with fp:
2857db96d56Sopenharmony_ci        lineno = 0
2867db96d56Sopenharmony_ci        while 1:
2877db96d56Sopenharmony_ci            line = fp.readline()
2887db96d56Sopenharmony_ci            if not line: break
2897db96d56Sopenharmony_ci            lineno = lineno + 1
2907db96d56Sopenharmony_ci            try:
2917db96d56Sopenharmony_ci                i = line.index('#')
2927db96d56Sopenharmony_ci            except ValueError:
2937db96d56Sopenharmony_ci                i = -1          # Happens to delete trailing \n
2947db96d56Sopenharmony_ci            words = line[:i].split()
2957db96d56Sopenharmony_ci            if not words: continue
2967db96d56Sopenharmony_ci            if len(words) == 3 and words[0] == 'struct':
2977db96d56Sopenharmony_ci                words[:2] = [words[0] + ' ' + words[1]]
2987db96d56Sopenharmony_ci            elif len(words) != 2:
2997db96d56Sopenharmony_ci                err(substfile + '%s:%r: warning: bad line: %r' % (substfile, lineno, line))
3007db96d56Sopenharmony_ci                continue
3017db96d56Sopenharmony_ci            if Reverse:
3027db96d56Sopenharmony_ci                [value, key] = words
3037db96d56Sopenharmony_ci            else:
3047db96d56Sopenharmony_ci                [key, value] = words
3057db96d56Sopenharmony_ci            if value[0] == '*':
3067db96d56Sopenharmony_ci                value = value[1:]
3077db96d56Sopenharmony_ci            if key[0] == '*':
3087db96d56Sopenharmony_ci                key = key[1:]
3097db96d56Sopenharmony_ci                NotInComment[key] = value
3107db96d56Sopenharmony_ci            if key in Dict:
3117db96d56Sopenharmony_ci                err('%s:%r: warning: overriding: %r %r\n' % (substfile, lineno, key, value))
3127db96d56Sopenharmony_ci                err('%s:%r: warning: previous: %r\n' % (substfile, lineno, Dict[key]))
3137db96d56Sopenharmony_ci            Dict[key] = value
3147db96d56Sopenharmony_ci
3157db96d56Sopenharmony_ciif __name__ == '__main__':
3167db96d56Sopenharmony_ci    main()
317