[Python-checkins] r56550 - sandbox/trunk/py_type_refactor sandbox/trunk/py_type_refactor/gen_enums.py sandbox/trunk/py_type_refactor/refactor.py

neal.norwitz python-checkins at python.org
Thu Jul 26 08:59:31 CEST 2007


Author: neal.norwitz
Date: Thu Jul 26 08:59:30 2007
New Revision: 56550

Added:
   sandbox/trunk/py_type_refactor/
   sandbox/trunk/py_type_refactor/gen_enums.py   (contents, props changed)
   sandbox/trunk/py_type_refactor/refactor.py   (contents, props changed)
Log:
Add a tool that can refactor C source files.  It can convert 
PyTypeObject and its associated structures that in the past have been
stored as static objects into function calls.  This conversion will 
allow easier upgrades by eliminating the worst parts of binary 
compatability.

It has been tested on all the .c files in the Objects subdirectory.
The updated files were visually inspected.  The new API has not been 
defined, so this just outputs something that is hopefully close enough 
until we can figure out the real API.



Added: sandbox/trunk/py_type_refactor/gen_enums.py
==============================================================================
--- (empty file)
+++ sandbox/trunk/py_type_refactor/gen_enums.py	Thu Jul 26 08:59:30 2007
@@ -0,0 +1,25 @@
+#!/usr/bin/env python
+
+"""Print to stdout the enums based on the values used in the fixer."""
+
+import sys
+
+import fixer
+
+
+def main(unused_argv):
+    indent = fixer.INITIAL_INDENT
+    for cls in fixer.StructParser.__subclasses__():
+        print 'typedef enum {'
+        METHODS = set(range(len(cls.FIELD_KIND)))
+        if hasattr(cls, '_METHOD_INDICES'):
+            METHODS = set(cls._METHOD_INDICES)
+        values = ['%s%s%s' % (indent, cls.FIELD_KIND_PREFIX, name)
+                  for i, name in enumerate(cls.FIELD_KIND) if i in METHODS]
+        print ',\n'.join(values)
+        print '} %sKind;' % cls.FIELD_KIND_PREFIX[:-1]
+        print
+
+
+if __name__ == '__main__':
+    main(sys.argv)

Added: sandbox/trunk/py_type_refactor/refactor.py
==============================================================================
--- (empty file)
+++ sandbox/trunk/py_type_refactor/refactor.py	Thu Jul 26 08:59:30 2007
@@ -0,0 +1,713 @@
+#!/usr/bin/env python
+
+"""A tool for converting PyTypeObject structures used in Python 2.5 and earlier
+to function calls used in 2.6 and above.
+
+usage:  refactor.py src1.c [src2.c] ...
+"""
+
+# TODO:
+#  * File re-writing outputs to new dir, rather than overwriting existing file.
+#  * Verify updated C files don't contain syntax errors.
+#  * Define real APIs and use them.
+
+
+import os
+import sys
+
+
+# The amount that code within a function should be indented.
+INITIAL_INDENT = '    '
+
+# Index at which code should be wrapped to the next line if possible.
+WRAP_INDEX = 76
+
+
+# TODO: use a command line option.
+_WRITE_FILE = False
+_DEBUG = False
+
+# These are the type structs we handle.
+_INTERESTING_TYPES = ['PyMethodDef', 'PyTypeObject',
+                      'PyMemberDef', 'PyGetSetDef',
+                      'PyNumberMethods', 'PySequenceMethods',
+                      'PyMappingMethods', 'PyBufferProcs',
+                     ]
+
+def LooksLikeDeclaration(line):
+    """Returns a bool whether the line looks like a declaration we might
+    be interested in.  False when looks like a parameter or variable decl.
+    """
+    line = line.rstrip()
+    # Include \\ because it looks like a macro and we aren't gonna handle it.
+    for c in '),;\\':
+        if c in line:
+            return True
+    return False
+
+
+def GetCsvTokens(data):
+    """Returns tokens from data that are broken at commas.
+    This can be used to break up data that is stored in static struct.
+
+    Yields:
+      strings that correspond to the tokens from data as separated by commas
+    """
+    Clean = lambda s: s.strip().replace('""', '')
+    start = index = 0
+    while 1:
+        comma_index = data.find(',', index)
+        paren_index = data.find('(', index)
+        quote_index = data.find('"', index)
+        # Determine which token we found first.
+        if quote_index >= 0 and quote_index < comma_index:
+            # It was a quote, eat everything up to the next quote.
+            # XXX: assume that there isn't a \" in there.
+            index = data.find('"', quote_index + 1)
+            if index >= 0:
+                index += 1
+        elif comma_index >= 0:
+            # Did we get a comma or paren first?
+            if paren_index >= 0 and paren_index < comma_index:
+                # Paren, find the close paren, ie ).
+                index = data.find(')', paren_index + 1)
+                if index >= 0:
+                    index += 1
+            else:
+                # Got a comma, clean up this data and move on to next token.
+                yield Clean(data[start:comma_index])
+                start = index = (comma_index + 1)
+        else:
+            # No quote, no comma, must be done.  Give 'em what we got.
+            yield Clean(data[start:])
+            break
+
+
+class GenericDef(object):
+    """Base class for all definitions.  Provides common utilities and API
+    for aggregating info about the definition and outputing code.
+    """
+
+    # Value which indicates the line contained a sentinel. 
+    SENTINEL = None
+
+    def __init__(self, name, is_static, first_line):
+        self.name = name
+        self.is_static = is_static
+        self.first_line = first_line
+        self.last_line = -1
+        self.lines = []
+
+    def AddLine(self, line, reader):
+        if line:
+            result = self.ParseLine(line, reader)
+            self.lines.append(result)
+
+    def ParseLine(self, unused_line, unused_reader):
+        return self.SENTINEL
+
+    def VerifyValid(self):
+        """Returns a bool whether everything seems consistent or not."""
+        # Verify there is a sentinel value, then remove it.
+        if not self.lines:
+            return False
+        if self.lines[-1] != self.SENTINEL:
+            return False
+        del self.lines[-1]
+        return True
+
+    def GetStatic(self):
+        if self.is_static:
+            return 'static '
+        return ''
+
+    def GetFunctionDeclaration(self):
+        return ('%svoid Init_%s(PyTypeObject *type)\n' %
+                (self.GetStatic(), self.name))
+
+    def NewCode(self):
+        """Returns a sequence of new lines of code to replace the old code."""
+        print "Can't produce new code", self.__class__.__name__
+        return []
+
+    def AppendParameter(self, line, parameter, indent):
+        """Append a parameter to a line reflowing if necessary.
+        The line should *not* end with a comma.
+
+        Args:
+          line: string of text
+          parameter: string
+          indent: int containing how much to indent next line if necessary
+
+        Returns:
+          new line of text with the proper prefix
+        """
+        nl_index = line.rfind('\n')
+        if nl_index < 0:
+            nl_index = 0
+
+        last_line_len = len(line[nl_index:])
+        prefix = ', '
+        if (last_line_len + len(parameter)) > WRAP_INDEX:
+            prefix = ',\n' + (' ' * indent)
+        return prefix + parameter
+
+
+class ArrayParser(GenericDef):
+
+    # Subclasses should set these values to something appropriate.
+
+    # These are used in ParseLine.
+    NUM_FIELDS = 0
+    SENTINEL = (None,) * NUM_FIELDS
+
+    # PREFIX is used in NewCode.
+    PREFIX = 'PyType_AddXXXArray('
+
+    def _GetOneDefinition(self, line, reader):
+        """Returns the full declaration line even if it originally spanned
+        multiple lines.  If the sentinel value is found, return None.
+
+        Raises:
+          NotImplementedError is raised if there is a problem parsing the line.
+        """
+        index = line.find('{')
+        if index < 0:
+            name = self.__class__.__name__
+            args = (name, reader.filename, reader.line_number, line)
+            raise NotImplementedError(
+                'Unable to find start of %s in %s:%d\n%s' % args)
+
+        # If we got a sentinel, just return what we got (ie, nothin).
+        line = line[index+1:].strip()
+        if line.startswith('0') or line.startswith('NULL'):
+            return None
+
+        if '}' not in line:
+            for count, next_line in reader.GetDefinition():
+                if count > 1:
+                    msg = ('Unable to find end of %s after two lines: %s' %
+                           (self.__class__.__name__, line))
+                    raise NotImplementedError(msg)
+                count += 1
+                line += next_line
+        return line
+
+    def ParseLine(self, line, reader):
+        """Returns the values separated by commas in the line of a struct
+        based on the number of possible values (NUM_FIELDS).  If any
+        values are missing, returns None for those values.
+
+        Returns:
+          sequence of length NUM_FIELDS
+            if a sentinel value was found, returns SENTINEL
+            if a preprocessor directive was found, returns the SENTINEL
+              with the last value changed the entire text of the line
+        """
+        if line[0] == '#':
+            return self.SENTINEL[:-1] + (line,)
+
+        line = self._GetOneDefinition(line, reader)
+        if line is None:
+            return self.SENTINEL
+
+        # Tokenize line and pull out the parts of the struct.
+        parts = GetCsvTokens(line)
+        pieces = filter(None, [s.strip().rstrip(',}') for s in parts])
+        if len(pieces) < self.NUM_FIELDS:
+            # Ensure we have the correct # of pieces, padded with None.
+            pieces.extend([None] * (self.NUM_FIELDS - len(pieces)))
+        assert len(pieces) == self.NUM_FIELDS, 'busted line: %r' % line
+        return pieces
+
+    def GetArgs(self, data):
+        return [arg or 'NULL' for arg in data]
+
+    def NewCode(self):
+        prefix = INITIAL_INDENT + self.PREFIX
+        INDENT = len(prefix)
+        yield self.GetFunctionDeclaration()
+        yield '{\n'
+        for data in self.lines:
+            name = data[0]
+            if name is None:
+                yield data[-1] + '\n'
+            else:
+                line = prefix + ('type, %s' % name)
+                for arg in self.GetArgs(data[1:]):
+                    line += self.AppendParameter(line, arg, INDENT)
+                line += ');\n'
+                yield line
+        yield '}\n'
+
+
+class GetSetDef(ArrayParser):
+    """Convert PyGetSetDef instances to method calls."""
+
+    PREFIX = 'PyType_AddGetSet('
+    NUM_FIELDS = 5
+    SENTINEL = (None,) * NUM_FIELDS
+
+    # Stores lines as 5-tuple that conforms to a PyGetSetDef.
+    #     (name, get, set, doc, closure)
+    # If there is a preprocessor statement, name will contain None
+    # and doc will contain the full line.
+
+
+class MemberDef(ArrayParser):
+    """Convert PyMemberDef instances to method calls."""
+
+    PREFIX = 'PyType_AddMember('
+    NUM_FIELDS = 5
+    SENTINEL = (None,) * NUM_FIELDS
+
+    # Stores lines as 5-tuple that conforms to a PyMemberDef.
+    #     (name, type, offset, flags, doc)
+    # If there is a preprocessor statement, name will contain None
+    # and doc will contain the full line.
+
+    def GetArgs(self, data):
+        args = [arg or 'NULL' for arg in data]
+        if args[2] == 'NULL':
+            args[2] = '0'
+        return args
+
+
+class MethodDef(ArrayParser):
+    """Convert PyMethodDef instances to method calls."""
+
+    PREFIX = 'PyType_AddMethod('
+    NUM_FIELDS = 4
+    SENTINEL = (None,) * NUM_FIELDS
+
+    # Stores lines as 4-tuple that conforms to a PyMethodDef.
+    #     (ml_name, ml_meth, ml_flags, ml_doc)
+    # If there is a preprocessor statement, name will contain None
+    # and ml_doc will contain the full line.
+
+    def GetArgs(self, data):
+        args = [arg or 'NULL' for arg in data]
+        if args[1] == 'NULL':
+            args[1] = 'METH_OLDARGS'
+        return args
+
+
+class StructParser(GenericDef):
+    # Subclasses should set these values to something appropriate.
+
+    # These are used in NewCode.
+    NUM_FIELDS = 0
+    PREFIX = 'PyType_AddXXXMethod('
+    # Sequence of enum names to be used as parameter to function called.
+    # Note: len(FIELD_KIND) == NUM_FIELDS
+    FIELD_KIND = []
+    # Prefix to apply to each FIELD_KIND.
+    FIELD_KIND_PREFIX = ''
+
+    def ParseLine(self, line, reader):
+        while ',' not in line and line.find('TPFLAGS') >= 0:
+            line += reader.GetField()
+        return line.rstrip(',')
+
+    def HandleSlotMethods(self, prefix, lines, indices):
+        kinds = self.FIELD_KIND
+        kind_prefix = self.FIELD_KIND_PREFIX
+        for i in indices:
+            data = lines[i]
+            if data and data != '0':
+                args = data
+                if kinds:
+                    args = '%s%s, %s' % (kind_prefix, kinds[i], data)
+                yield prefix + ('type, %s);\n' % args)
+
+    def NewCode(self):
+        assert len(self.FIELD_KIND) == self.NUM_FIELDS, \
+               ('%s: %d != %d' %
+                (self.__class__.__name__, len(self.FIELD_KIND), self.NUM_FIELDS))
+
+        prefix = INITIAL_INDENT + self.PREFIX
+
+        yield self.GetFunctionDeclaration()
+        yield '{\n'
+        for line in self.HandleSlotMethods(prefix, self.lines,
+                                           range(len(self.lines))):
+            yield line
+        yield '}\n'
+
+
+class NumberMethods(StructParser):
+    """Convert PyNumberMethods instances to method calls."""
+
+    PREFIX = 'PyType_AddNumberMethod('
+    NUM_FIELDS = 39
+    FIELD_KIND_PREFIX = 'PyNumberMethod_'
+    FIELD_KIND = ['Add', 'Subtract', 'Multiply', 'Divide', 'Remainder',
+                  'DivMod', 'Power', 'Negative', 'Positive', 'Absolute',
+                  'NonZero', 'Invert', 'LShift', 'RShift',
+                  'And', 'Xor', 'Or', 'Coerce',
+                  'Int', 'Long', 'Float', 'Oct', 'Hex',
+                  'InplaceAdd', 'InplaceSubtract', 'InplaceMultiply',
+                  'InplaceDivide', 'InplaceRemainder',
+                  'InplacePower', 'InplaceLShift', 'InplaceRShift',
+                  'InplaceAnd', 'InplaceXor', 'InplaceOr',
+                  'FloorDivide', 'TrueDivide',
+                  'InplaceFloorDivide', 'InplaceTrueDivide',
+                  'Index'
+                 ]
+
+
+class SequenceMethods(StructParser):
+    """Convert PySequenceMethods instances to method calls."""
+
+    PREFIX = 'PyType_AddSequenceMethod('
+    NUM_FIELDS = 10
+    FIELD_KIND_PREFIX = 'PySequenceMethod_'
+    FIELD_KIND = ['Length', 'Concat', 'Repeat', 'Item', 'Slice',
+                   'AssignItem', 'AssignSlice', 'Contains',
+                   'InplaceConcat', 'InplaceRepeat']
+
+
+class MappingMethods(StructParser):
+    """Convert PyMappingMethods instances to method calls."""
+
+    PREFIX = 'PyType_AddMappingMethod('
+    NUM_FIELDS = 3
+    FIELD_KIND_PREFIX = 'PyMappingMethod_'
+    FIELD_KIND = ['Length', 'Subscript', 'SetItem']
+
+
+class BufferProcs(StructParser):
+    """Convert PyBufferProcs instances to method calls."""
+
+    PREFIX = 'PyType_AddBufferProc('
+    NUM_FIELDS = 4
+    FIELD_KIND_PREFIX = 'PyBufferMethod_'
+    FIELD_KIND = ['Read', 'Write', 'SegmentCount', 'CharBuffer']
+
+
+class TypeDef(StructParser):
+    """Convert PyTypeObject instances to method calls."""
+
+    PREFIX = 'PyType_AddMethod('
+    NUM_FIELDS = 45
+    FIELD_KIND_PREFIX = 'PyTypeMethod_'
+    FIELD_KIND = ['Name', 'BasicSize', 'ItemSize',
+                  'Dealloc', 'Print', 'Getattr', 'Setattr', 'Compare', 'Repr',
+                  'AsNumber', 'AsSequence', 'AsMapping',
+                  'Hash', 'Call', 'Str', 'Getattro', 'Setattro',
+                  'AsBuffer', 'Flags', 'Doc', 'Traverse', 'Clear',
+                  'RichCompare', 'WeaklistOffset', 'Iter', 'IterNext',
+                  'Methods', 'Members', 'GetSet', 'Base', 'Dict',
+                  'DescrGet', 'DescrSet', 'DictOffset',
+                  'Init', 'Alloc', 'New', 'Free', 'IsGc',
+                  'Bases', 'MRO', 'Cache', 'Subclasses', 'Weaklist', 'Del'
+                 ]
+    _METHOD_INDICES = [3, 4, 5, 6, 7, 8, 12, 13, 14, 15, 16, 20, 21, 22,
+                       24, 25, 31, 32, 34, 35, 36, 37, 38, 44]
+
+    def NewCode(self):
+        assert len(self.FIELD_KIND) == self.NUM_FIELDS, \
+               ('%s: %d != %d' %
+                (self.__class__.__name__, len(self.FIELD_KIND), self.NUM_FIELDS))
+
+        # Find the first quoted line, that's the name which starts the type.
+        while self.lines and self.lines[0][0] != '"':
+            # Who cares if this is slow.  There aren't that many elements.
+            self.lines.pop(0)
+
+        # Ensure we have the proper number of values.
+        lines = self.lines[:]
+        lines.extend([None] * (self.NUM_FIELDS - len(lines)))
+        prefix = INITIAL_INDENT + self.PREFIX
+
+        yield '%sPyTypeObject %s;\n' % (self.GetStatic(), self.name)
+        yield '\n'
+        yield self.GetFunctionDeclaration()
+        yield '{\n'
+        # TODO: Handle: ItemSize: 2?, WeaklistOffset: 23?
+        name = lines[0]
+        basic_size = lines[1]
+        flags = lines[18]
+        doc = lines[19]
+        base = lines[29]
+        line = INITIAL_INDENT + '_PyType_Init(type'
+        indent = len(line) - len(INITIAL_INDENT)
+        for arg in (name, basic_size, flags, base, doc):
+            if not arg:
+                arg = '0'
+            line += self.AppendParameter(line, arg, indent)
+        line += ');\n'
+        yield line
+
+        def FormatInitCall(name):
+            if name[0] == '&':
+                name = name[1:]
+            return '%sInit_%s(type);\n' % (INITIAL_INDENT, name)
+
+        # Handle all the methods, members, getsets.
+        # These are special because we need to call the other initializers.
+        as_number = lines[9]
+        if as_number and as_number != '0':
+            yield FormatInitCall(as_number)
+        as_sequence = lines[10]
+        if as_sequence and as_sequence != '0':
+            yield FormatInitCall(as_sequence)
+        as_mapping = lines[11]
+        if as_mapping and as_mapping != '0':
+            yield FormatInitCall(as_mapping)
+        as_buffer = lines[17]
+        if as_buffer and as_buffer != '0':
+            yield FormatInitCall(as_buffer)
+
+        methods = lines[26]
+        if methods and methods != '0':
+            yield FormatInitCall(methods)
+        members = lines[27]
+        if members and members != '0':
+            yield FormatInitCall(members)
+        getsets = lines[28]
+        if getsets and getsets != '0':
+            yield FormatInitCall(getsets)
+
+        # TODO: ignore these values for now.  Are they are set at runtime?
+        # Dict, 30, Bases: 39, MRO: 40, Cache: 41, Subclasses:42, Weaklist: 43
+
+        # Handle all the __special__ methods (ie, slots).
+        methods = self.HandleSlotMethods(prefix, lines, self._METHOD_INDICES)
+        for line in methods:
+            yield line
+        yield '}\n'
+
+        yield '/* Move this code to an initialization routine.\n'
+        yield INITIAL_INDENT + 'Init_%s(&%s);\n' % (self.name, self.name)
+        yield ' * End of code that must be moved. */\n'
+
+class Fixer(object):
+    def __init__(self, filename):
+        self.filename = filename
+        self.line_number = 0
+        self.fp = open(filename)
+
+        # {name: GenericDef}
+        self.method_defs = {}
+        self.number_defs = {}
+        self.seq_defs = {}
+        self.mapping_defs = {}
+        self.buffer_defs = {}
+        self.member_defs = {}
+        self.getset_defs = {}
+        self.type_defs = {}
+
+    def Close(self):
+        self.fp.close()
+
+    def Fix(self):
+        for line in self.fp:
+            start_line = self.line_number
+            self.line_number += 1
+            line = self._StripComments(line)
+            if not line:
+                continue
+            # Skip preprocessor lines.
+            if line.lstrip().startswith('#'):
+                continue
+
+            # Looks like we got code.  Anything we are interested in?
+            for name in _INTERESTING_TYPES:
+                index = line.find(name)
+                if index >= 0 and not LooksLikeDeclaration(line):
+                    line = line.rstrip()
+                    ok, line = self._ShouldProcess(line)
+                    if ok:
+                        self._HandleLine(line, index, name, start_line)
+                        break
+
+    def _HandleLine(self, line, index, name, start_line):
+        if _DEBUG: print 'Found %s:%d %s' % (name, self.line_number, line)
+        str_before_type = line[:index].strip()
+        var_name = line[index+len(name):].strip().split()[0]
+        var_name = var_name.rstrip(' []')
+        is_static = (str_before_type and
+                     str_before_type.split()[0] == 'static')
+        # Lookup the method to handle this type and call it.
+        getattr(self, 'Handle' + name)(var_name, is_static, start_line)
+
+    def PrintNewMethods(self):
+        if not _DEBUG:
+            return
+
+        def Printer(container, name):
+            if container:
+                print name, 'from', self.filename
+                for name, method in container.items():
+                    print 'Lines %d-%d' % (method.first_line, method.last_line)
+                    print ''.join(method.NewCode())
+
+        Printer(self.getset_defs, 'GetSet:')
+        Printer(self.member_defs, 'Members:')
+        Printer(self.method_defs, 'Methods:')
+        Printer(self.number_defs, 'NumberMethods:')
+        Printer(self.seq_defs, 'SequenceMethods:')
+        Printer(self.mapping_defs, 'MappingMethods:')
+        Printer(self.buffer_defs, 'BufferProcs:')
+        Printer(self.type_defs, 'Types:')
+
+    def RewriteFile(self):
+        # Read the entire file into a list.
+        self.fp.seek(0, 0)
+        lines = self.fp.readlines()
+
+        # Update lines with new data.
+        defs = [self.getset_defs, self.member_defs, self.method_defs,
+                self.number_defs, self.seq_defs, self.mapping_defs,
+                self.buffer_defs, self.type_defs ]
+        # Sort the changed lines from highest line to lowest, so replacing
+        # is done from the beginning to the end and we don't have to worry
+        # about shifting lines internally if we add/remove them.
+        line_numbers = [(c.first_line, c) for d in defs for c in d.values()]
+        for _, container in sorted(line_numbers, reverse=True):
+            new_lines = container.NewCode()
+            lines[container.first_line:container.last_line] = new_lines
+
+        # Construct the new directory and ensure it exists.
+        base_filename = os.path.basename(self.filename)
+        path = os.path.join(os.path.dirname(self.filename), 'new')
+        try:
+            os.mkdir(path)
+        except OSError:
+            pass  # TODO: Assume the directory is there and writable for now.
+
+        # Output the new file.
+        output_fp = open(os.path.join(path, base_filename), 'w')
+        try:
+            output_fp.writelines(lines)
+        finally:
+            output_fp.close()
+
+    def _ShouldProcess(self, line):
+        if line[-1] == '{':
+            return True, line
+
+        next_line = self.fp.next()
+        self.line_number += 1
+        if LooksLikeDeclaration(next_line):
+            return False, '%s %s' % (line, next_line)
+
+        result = next_line.rstrip()[-1] == '{'
+        if not result:
+            print 'Ambiguous %s %d: %s' % (self.filename, self.line_number, line)
+            print 'next_line', next_line
+        return result, '%s %s' % (line, next_line)
+
+    def _StripComments(self, line):
+        index = line.find('//')
+        if index >= 0:
+            # Return the line up to the comment.
+            return line[:index]
+
+        index = line.find('/*')
+        if index < 0:
+            # No comment.
+            return line
+
+        comment_end = line.find('*/', index)
+        if comment_end >= 0:
+            # Recurse in case there are multiple comments on the line.
+            return self._StripComments(line[:index] + line[comment_end+2])
+
+        # The comment doesn't end on the same line, find the end.
+        for line in self.fp:
+            self.line_number += 1
+            index = line.find('*/')
+            if index >= 0:
+                # Recurse in case there are multiple comments on the line.
+                return self._StripComments(line[index+2:])
+        raise RuntimeError('End of file reached while processing comment.')
+
+    def GetField(self):
+        # XXX: This is a hack.  Just return the next line and let the caller
+        # take care of determining if this ends the field or not.
+        self.line_number += 1
+        return self._StripComments(self.fp.next()).strip()
+
+    def GetDefinition(self):
+        curly_count = 1
+        count = 0
+        line = ''
+        for line in self.fp:
+            self.line_number += 1
+            line = self._StripComments(line).strip()
+            if '{' in line:
+                curly_count += 1
+            if '}' in line:
+                curly_count -= 1
+
+            # Finished when the openning brace is closed.
+            if curly_count == 0:
+                break
+
+            yield count, line
+            count += 1
+
+        # The last line may be partial, pass that along too.
+        if not line.lstrip().startswith('}'):
+            yield count, line
+
+    # These Handle methods process each type.  They will read all the lines
+    # and print out the lines that should be used to replace the old code.
+
+    def _HandleDefinition(self, def_object, container):
+        for count, line in self.GetDefinition():
+            if line.lstrip().startswith('}'):
+                break
+            def_object.AddLine(line, self)
+        def_object.VerifyValid()
+        def_object.last_line = self.line_number
+        container[def_object.name] = def_object
+
+    _HandleArrayDefinition = _HandleDefinition
+
+    def HandlePyGetSetDef(self, name, is_static, start_line):
+        def_object = GetSetDef(name, is_static, start_line)
+        self._HandleArrayDefinition(def_object, self.getset_defs)
+
+    def HandlePyMemberDef(self, name, is_static, start_line):
+        def_object = MemberDef(name, is_static, start_line)
+        self._HandleArrayDefinition(def_object, self.member_defs)
+
+    def HandlePyMethodDef(self, name, is_static, start_line):
+        def_object = MethodDef(name, is_static, start_line)
+        self._HandleArrayDefinition(def_object, self.method_defs)
+
+    def HandlePyNumberMethods(self, name, is_static, start_line):
+        def_object = NumberMethods(name, is_static, start_line)
+        self._HandleDefinition(def_object, self.number_defs)
+
+    def HandlePySequenceMethods(self, name, is_static, start_line):
+        def_object = SequenceMethods(name, is_static, start_line)
+        self._HandleDefinition(def_object, self.seq_defs)
+
+    def HandlePyMappingMethods(self, name, is_static, start_line):
+        def_object = MappingMethods(name, is_static, start_line)
+        self._HandleDefinition(def_object, self.mapping_defs)
+
+    def HandlePyBufferProcs(self, name, is_static, start_line):
+        def_object = BufferProcs(name, is_static, start_line)
+        self._HandleDefinition(def_object, self.buffer_defs)
+
+    def HandlePyTypeObject(self, name, is_static, start_line):
+        def_object = TypeDef(name, is_static, start_line)
+        self._HandleDefinition(def_object, self.type_defs)
+
+
+def main(argv):
+    for filename in argv[1:]:
+        fixer = Fixer(filename)
+        fixer.Fix()
+        fixer.PrintNewMethods()
+        fixer.RewriteFile()
+        fixer.Close()
+
+
+if __name__ == '__main__':
+    main(sys.argv)


More information about the Python-checkins mailing list