add-gcobject-inheritance.py
author Benjamin Smedberg <benjamin@smedbergs.us>
Sat, 26 Jul 2008 22:49:39 -0400
changeset 167 a4da40849f5436e629c5732f4368c6c48189637f
parent 129 09bc16d43f7bf6f49ed07aa09802a6c8e2b41b03
permissions -rw-r--r--
State as of now

#!/usr/bin/env python

"""Feed this script the concatenated output of gcobject.js
from a bunch of files and it will
produce a patch causing the relevant objects to inherit from
XPCOMGCFinalizedObject."""

import sys
import re
import os

lineSplitter = re.compile(r'(?<=\n)')

def writePatchHunk(oldText, newText, file, lineno):
    """Write a hunk in unified diff format (with no context)"""
    oldLines = oldText.splitlines(True)
    newLines = newText.splitlines(True)
    print "--- %s" % file
    print "+++ %s" % file
    print "@@ -%(lineno)s,%(oldcount)s +%(lineno)s,%(newcount)s @@" % \
	{'lineno': lineno,
	 'oldcount': len(oldLines),
	 'newcount': len(newLines) }
    for line in oldLines:
	print "-%s" % line,
    for line in newLines:
	print "+%s" % line,

specialDecls = ['NS_DECL_EDITOR_COMMAND',
		'NS_DECL_COMPOSER_COMMAND',
		'NS_DECL_CLIPBOARD_COMMAND']

def ClassName(qname):
    l = qname.split('::')
    return l[len(l) - 1]

classFinder = re.compile(r'(class|struct)\s')

replacer = re.compile(r"""
(?P<decl>\s*(?:struct|class)\s+(?:NS_COM|NS_COM_GLUE|NS_GFX)?\s*(?P<classname>\w+)\s*:\s*)
(?P<bases>(?:(?:public|private|protected)?\s*\w+\s*,\s*)*
(?:public|private|protected)?\s*\w+\s*,?\s*)$
""", re.X)

baseSplitter = re.compile(r'(\s*,\s*)')

visFinder = re.compile(r'private|public|protected')
commentFinder = re.compile(r'#if|\s*//')

def writePatch(classname, file, lineno):
    fd = open(file, 'r')
    file_lines = fd.readlines()
    fd.close()

    # CPP line numbers are 1-based
    startLine = int(lineno) - 1

    # But GCC reports the declaration location as the location of the opening
    # brace... so walk backwards looking for a "class"
    while startLine > 0:
        line = file_lines[startLine]
        if classFinder.search(line) is not None:
            break

	# Special-case stupidity
	for decl in specialDecls:
	    if line.startswith(decl):
                print >>sys.stderr, "Skipping class %s because of special declaration" % classname
		return

        startLine -= 1

    lineno = startLine
    declText = ''

    while True:
	line = file_lines[lineno]

	if commentFinder.match(line):
	    declExtra = ''
	    break

	bracketPos = line.find('{')
	if bracketPos != -1:
	    declText += line[:bracketPos]

	    # Save the rest of the line to create a useful patch below
	    declExtra = line[bracketPos:]
	    break
	else:
	    declText += line
	lineno += 1

    m = replacer.match(declText)
    if m is None:
        surround = "".join(file_lines[startLine - 2:startLine + 2])
	raise Error("File %s line %s didn't match:\n%s\nsurrounding lines:\n%s" % (file, startLine, declText, surround))
    

    decl, foundClassname, bases = \
	m.group('decl', 'classname', 'bases')

    if ClassName(classname) != foundClassname:
	raise Error("At %s found class %s expected %s" % (decl, foundClassname, classname))

    # this will split into a list base1, split1, base2, split2...
    baseList = baseSplitter.split(bases)

    # if the visibility of the first base was not specified,
    # specify it as "private" which is the default
    if not visFinder.match(baseList[0]):
	baseList[0] = 'private ' + baseList[0]

    if len(baseList) == 1:
	newSplit = ', '
    else:
	newSplit = baseList[1]

    baseList[0:0] = ['public XPCOMGCFinalizedObject', newSplit]

    writePatchHunk(declText + declExtra,
		   decl + ''.join(baseList) + declExtra,
		   file, startLine)

# First, we read all the lines and parse them into a class map. Classes
# which inherit from other classes are removed before processing.

exceptions = ()

classes = {}

def set_class(name, file, line, bases):
    if name in classes:
	if classes[name]['file'] != file or classes[name]['line'] != line:
	    print >>sys.stderr, "Class '%s' has different declarations:\n* %s\n* %s" % \
		(name,
		 (file, line),
		 (classes[name]['file'], classes[name]['line']))
	    return

	if classes[name]['bases'] != bases:
	    raise Error("Class '%s' has different bases:\n* %s\n* %s" %
		(name,
		 bases,
		 classes[name]['bases']))
    else:
	classes[name] = {'file': file,
                         'line': line,
			 'bases': bases}

(objdir, ) = sys.argv[1:]

def walk_gcobject_results(dir):
    for (dirpath, dirnames, filenames) in os.walk(dir):
        for file in filenames:
            if file.endswith(".gcobject-results"):
                yield (dirpath, "%s/%s" % (dirpath, file))

for (dirpath, file) in walk_gcobject_results(objdir):
    fd = open(file)
    for line in fd:
        line = line.strip()
        c, decl, baselist = line.split('\t')
        declfile, lineno = decl.split(':')
        declfile = os.path.join(dirpath, declfile)
        declfile = os.path.realpath(declfile)
        bases = baselist.split(',')

        set_class(c, declfile, lineno, bases)

    fd.close()

for e in exceptions:
    classes.pop(e, None)

for c in classes:
    process = True
    for b in classes[c]['bases']:
	if b in classes:
	    print >>sys.stderr, "Not processing %s: inherits from %s" % (c, b)
 	    process = False
	    break

    if process:
	writePatch(c, classes[c]['file'], classes[c]['line'])