#!/usr/bin/env python3
"""Remove 'static' from ATK class method definitions.
The class preprocessor emits non-static extern declarations for all class
methods in .eh files. When the .c implementation defines a method as 'static',
modern clang rejects it: "static declaration follows non-static declaration."
Removing 'static' from the definition is correct: class methods need external
linkage so the dispatch table and dlopen-based dynamic loader can find them.
Two K&R definition patterns are handled:
Pattern A (static on same line as method):
static struct foo *classname__Method(self, a, b)
→ struct foo *classname__Method(self, a, b)
Pattern B (K&R split: static+type on own line, name on next):
static boolean
classname__Method(self, a, b)
→
boolean
classname__Method(self, a, b)
Only targets function names containing __ (double-underscore ATK class methods).
Does not touch single-name helpers like 'initself' or 'DrawWormHole' — those
need forward-declaration fixes instead.
Usage:
fix-static-methods [--dry-run] file.c [file2.c ...]
fix-static-methods [--dry-run] --dir src/atk/text
"""
import re
import sys
import argparse
import os
# Matches a class method name: prefix__Suffix or prefix_Suffix (ATK convention)
# Must start at column 0 (no leading whitespace) in K&R style definitions.
CLASS_METHOD = re.compile(r'^[A-Za-z_][A-Za-z0-9_]*__[A-Za-z0-9_]+\s*\(')
# Pattern A: static keyword at line start, followed by return type and method name
# e.g. "static struct environment *text__AlwaysWrapViewChar("
PATTERN_A = re.compile(
r'^(static\s+)'
r'((?:struct\s+\w+\s*\*\s*|(?:unsigned\s+)?(?:long\s+)?(?:int|char|void|boolean|short|long|float|double)\s*\*?\s*)?)'
r'([A-Za-z_][A-Za-z0-9_]*__[A-Za-z0-9_]+\s*\()',
re.MULTILINE
)
def fix_file(path, dry_run=False):
with open(path, 'r', errors='replace') as f:
lines = f.readlines()
changed = False
out = []
i = 0
while i < len(lines):
line = lines[i]
# Pattern A: 'static ... classname__Method(' on one line
m = re.match(r'^(static\s+)(?=.*\b[A-Za-z_][A-Za-z0-9_]*__[A-Za-z0-9_]+\s*\()', line)
if m:
# Strip only the 'static ' prefix -- keep everything after it
# (parameter names, closing paren, etc.) untouched.
out.append(line[m.end(1):])
changed = True
i += 1
continue
# Pattern B: 'static TYPE' on this line, class method name starts next line
m = re.match(r'^(\s*)static\s+(\S.*\S|\S)\s*$', line.rstrip('\n'))
if m and i + 1 < len(lines) and CLASS_METHOD.match(lines[i + 1]):
indent = m.group(1)
return_type = m.group(2)
out.append(indent + return_type + '\n')
changed = True
i += 1
continue
out.append(line)
i += 1
if changed:
if not dry_run:
with open(path, 'w') as f:
f.writelines(out)
return True
return False
# Platform-specific directories that aren't compiled on Darwin
SKIP_DIRS = {
'hp300', 'hp700', 'hp800', 'pmax_3', 'pmax_41', 'pmax_42', 'pmax_ul4',
'rt_aos4', 'rt_mach', 'sun3_35', 'sun3_4', 'sun3_41', 'sun4_40',
'sun4_41', 'sun4_51', 'vax_3', 'vax_43', 'aix_i386', 'aix_rt', 'aos_rt',
'dec_mips', 'dec_mips_42', 'dec_vax', 'hp_68k', 'i386_bsd', 'i386_bsdi',
'i386_Linux', 'i386_mach', 'mach_rt', 'sco_i386', 'sgi_mips', 'sun_68k',
'sun_sparc', 'sun_sparc_mach', 'rs_aix3',
}
def main():
parser = argparse.ArgumentParser(description=__doc__.splitlines()[0])
parser.add_argument('--dry-run', action='store_true',
help='Show what would change without modifying files')
parser.add_argument('--dir', metavar='DIR',
help='Process all .c files under DIR recursively')
parser.add_argument('files', nargs='*', metavar='file.c',
help='C source files to process')
args = parser.parse_args()
targets = list(args.files)
if args.dir:
for dirpath, dirnames, filenames in os.walk(args.dir):
dirnames[:] = [d for d in dirnames if d not in SKIP_DIRS]
for fn in filenames:
if fn.endswith('.c'):
targets.append(os.path.join(dirpath, fn))
if not targets:
parser.print_help()
sys.exit(1)
total = 0
for path in targets:
if fix_file(path, dry_run=args.dry_run):
verb = 'Would modify' if args.dry_run else 'Modified'
print(f'{verb}: {path}')
total += 1
print(f'\nTotal files {"that would be " if args.dry_run else ""}modified: {total}')
if __name__ == '__main__':
main()