summaryrefslogtreecommitdiffstats
path: root/Tools
diff options
context:
space:
mode:
authorJeremy Hylton <jeremy@alum.mit.edu>2002-08-05 18:29:45 (GMT)
committerJeremy Hylton <jeremy@alum.mit.edu>2002-08-05 18:29:45 (GMT)
commit53d527ad1828d484e336626ee59d7d5606b58499 (patch)
tree14a9ee83fb0e0ede7648632e437886f3a21001cb /Tools
parentf4d32df19d0f43b7e6b9479a2aac7c80d8c7cf28 (diff)
downloadcpython-53d527ad1828d484e336626ee59d7d5606b58499.zip
cpython-53d527ad1828d484e336626ee59d7d5606b58499.tar.gz
cpython-53d527ad1828d484e336626ee59d7d5606b58499.tar.bz2
Initial prototype of framer: a tool to build the frame for extension modules.
Diffstat (limited to 'Tools')
-rw-r--r--Tools/framer/README.txt8
-rw-r--r--Tools/framer/TODO.txt6
-rw-r--r--Tools/framer/example.py127
-rw-r--r--Tools/framer/framer/__init__.py8
-rw-r--r--Tools/framer/framer/bases.py221
-rw-r--r--Tools/framer/framer/function.py173
-rw-r--r--Tools/framer/framer/member.py73
-rw-r--r--Tools/framer/framer/slots.py64
-rw-r--r--Tools/framer/framer/struct.py52
-rw-r--r--Tools/framer/framer/structparse.py46
-rw-r--r--Tools/framer/framer/template.py102
-rw-r--r--Tools/framer/framer/util.py35
12 files changed, 915 insertions, 0 deletions
diff --git a/Tools/framer/README.txt b/Tools/framer/README.txt
new file mode 100644
index 0000000..4a93a4d
--- /dev/null
+++ b/Tools/framer/README.txt
@@ -0,0 +1,8 @@
+framer is a tool to generate boilerplate code for C extension types.
+
+The boilerplate is generated from a specification object written in
+Python. The specification uses the class statement to describe the
+extension module and any extension types it contains. From the
+specification, framer can generate all the boilerplate C code,
+including function definitions, argument handling code, and type
+objects.
diff --git a/Tools/framer/TODO.txt b/Tools/framer/TODO.txt
new file mode 100644
index 0000000..8586c8e
--- /dev/null
+++ b/Tools/framer/TODO.txt
@@ -0,0 +1,6 @@
+Add spec for getsets.
+Generate a distutils setup script.
+Handle operator overloading.
+Generate traverse and clear methods for GC.
+Handle mapping, sequence, buffer protocols.
+Finish the todo list.
diff --git a/Tools/framer/example.py b/Tools/framer/example.py
new file mode 100644
index 0000000..b77f475
--- /dev/null
+++ b/Tools/framer/example.py
@@ -0,0 +1,127 @@
+"""Generate the skeleton for cStringIO as an example of framer."""
+
+from framer.bases import Module, Type
+from framer.member import member
+
+class cStringIO(Module):
+ """A simple fast partial StringIO replacement.
+
+ This module provides a simple useful replacement for the StringIO
+ module that is written in C. It does not provide the full
+ generality of StringIO, but it provides enough for most
+ applications and is especially useful in conjunction with the
+ pickle module.
+
+ Usage:
+
+ from cStringIO import StringIO
+
+ an_output_stream = StringIO()
+ an_output_stream.write(some_stuff)
+ ...
+ value = an_output_stream.getvalue()
+
+ an_input_stream = StringIO(a_string)
+ spam = an_input_stream.readline()
+ spam = an_input_stream.read(5)
+ an_input_stream.seek(0) # OK, start over
+ spam = an_input_stream.read() # and read it all
+ """
+
+ __file__ = "cStringIO.c"
+
+ def StringIO(o):
+ """Return a StringIO-like stream for reading or writing"""
+ StringIO.pyarg = "|O"
+
+ class InputType(Type):
+ "Simple type for treating strings as input file streams"
+
+ abbrev = "input"
+
+ struct = """\
+ typedef struct {
+ PyObject_HEAD
+ char *buf;
+ int pos;
+ int size;
+ PyObject *pbuf;
+ } InputObject;
+ """
+
+ def flush(self):
+ """Does nothing"""
+
+ def getvalue(self):
+ """Get the string value.
+
+ If use_pos is specified and is a true value, then the
+ string returned will include only the text up to the
+ current file position.
+ """
+
+ def isatty(self):
+ """Always returns False"""
+
+ def read(self, s):
+ """Return s characters or the rest of the string."""
+ read.pyarg = "|i"
+
+ def readline(self):
+ """Read one line."""
+
+ def readlines(self, hint):
+ """Read all lines."""
+ readlines.pyarg = "|i"
+
+ def reset(self):
+ """Reset the file position to the beginning."""
+
+ def tell(self):
+ """Get the current position."""
+
+ def truncate(self, pos):
+ """Truncate the file at the current position."""
+ truncate.pyarg = "|i"
+
+ def seek(self, position, mode=0):
+ """Set the current position.
+
+ The optional mode argument can be 0 for absolute, 1 for relative,
+ and 2 for relative to EOF. The default is absolute.
+ """
+ seek.pyarg = "i|i"
+
+ def close(self):
+ pass
+
+ class OutputType(InputType):
+ "Simple type for output strings."
+
+ abbrev = "output"
+
+ struct = """\
+ typedef struct {
+ PyObject_HEAD
+ char *buf;
+ int pos;
+ int size;
+ int softspace;
+ } OutputObject;
+ """
+
+ softspace = member()
+
+ def close(self):
+ """Explicitly release resources."""
+
+ def write(self, s):
+ """Write a string to the file."""
+ # XXX Hack: writing None resets the buffer
+
+ def writelines(self, lines):
+ """Write each string in lines."""
+
+
+cStringIO.gen()
+
diff --git a/Tools/framer/framer/__init__.py b/Tools/framer/framer/__init__.py
new file mode 100644
index 0000000..ab73a30
--- /dev/null
+++ b/Tools/framer/framer/__init__.py
@@ -0,0 +1,8 @@
+"""A tool to generate basic framework for C extension types.
+
+The basic ideas is the same as modulator, but the code generates code
+using many of the new features introduced in Python 2.2. It also
+takes a more declarative approach to generating code.
+"""
+
+
diff --git a/Tools/framer/framer/bases.py b/Tools/framer/framer/bases.py
new file mode 100644
index 0000000..61052ab
--- /dev/null
+++ b/Tools/framer/framer/bases.py
@@ -0,0 +1,221 @@
+"""Provides the Module and Type base classes that user code inherits from."""
+
+__all__ = ["Module", "Type", "member"]
+
+from framer import struct, template
+from framer.function import Function, Method
+from framer.member import member
+from framer.slots import *
+from framer.util import cstring, unindent
+
+from types import FunctionType
+
+def sortitems(dict):
+ L = dict.items()
+ L.sort()
+ return L
+
+# The Module and Type classes are implemented using metaclasses,
+# because most of the methods are class methods. It is easier to use
+# metaclasses than the cumbersome classmethod() builtin. They have
+# class methods because they are exposed to user code as base classes.
+
+class BaseMetaclass(type):
+ """Shared infrastructure for generating modules and types."""
+
+ # just methoddef so far
+
+ def dump_methoddef(self, f, functions, vars):
+ def p(templ, vars=vars): # helper function to generate output
+ print >> f, templ % vars
+
+ if not functions:
+ return
+ p(template.methoddef_start)
+ for name, func in sortitems(functions):
+ if func.__doc__:
+ p(template.methoddef_def_doc, func.vars)
+ else:
+ p(template.methoddef_def, func.vars)
+ p(template.methoddef_end)
+
+class ModuleMetaclass(BaseMetaclass):
+ """Provides methods for Module class."""
+
+ def gen(self):
+ self.analyze()
+ self.initvars()
+ f = open(self.__filename, "w")
+ self.dump(f)
+ f.close()
+
+ def analyze(self):
+ self.name = getattr(self, "abbrev", self.__name__)
+ self.__functions = {}
+ self.__types = {}
+ self.__members = False
+
+ for name, obj in self.__dict__.iteritems():
+ if isinstance(obj, FunctionType):
+ self.__functions[name] = Function(obj, self)
+ elif isinstance(obj, TypeMetaclass):
+ obj._TypeMetaclass__module = self.name
+ obj.analyze()
+ self.__types[name] = obj
+ if obj.has_members():
+ self.__members = True
+
+ def initvars(self):
+ v = self.__vars = {}
+ filename = getattr(self, "__file__", None)
+ if filename is None:
+ filename = self.__name__ + "module.c"
+ self.__filename = v["FileName"] = filename
+ name = v["ModuleName"] = self.__name__
+ v["MethodDefName"] = "%s_methods" % name
+ v["ModuleDocstring"] = cstring(unindent(self.__doc__))
+
+ def dump(self, f):
+ def p(templ, vars=self.__vars): # helper function to generate output
+ print >> f, templ % vars
+
+ p(template.module_start)
+ if self.__members:
+ p(template.member_include)
+ print >> f
+
+ if self.__doc__:
+ p(template.module_doc)
+
+ for name, type in sortitems(self.__types):
+ type.dump(f)
+
+ for name, func in sortitems(self.__functions):
+ func.dump(f)
+
+ self.dump_methoddef(f, self.__functions, self.__vars)
+
+ p(template.module_init_start)
+ for name, type in sortitems(self.__types):
+ type.dump_init(f)
+
+ p("}")
+
+class Module:
+ __metaclass__ = ModuleMetaclass
+
+class TypeMetaclass(BaseMetaclass):
+
+ def dump(self, f):
+ self.initvars()
+
+ # defined after initvars() so that __vars is defined
+ def p(templ, vars=self.__vars):
+ print >> f, templ % vars
+
+ if self.struct is not None:
+ print >> f, unindent(self.struct, False)
+
+ if self.__doc__:
+ p(template.docstring)
+
+ for name, func in sortitems(self.__methods):
+ func.dump(f)
+
+ self.dump_methoddef(f, self.__methods, self.__vars)
+ self.dump_memberdef(f)
+ self.dump_slots(f)
+
+ def has_members(self):
+ if self.__members:
+ return True
+ else:
+ return False
+
+ def analyze(self):
+ # called by ModuleMetaclass analyze()
+ self.name = getattr(self, "abbrev", self.__name__)
+ src = getattr(self, "struct", None)
+ if src is not None:
+ self.__struct = struct.parse(src)
+ else:
+ self.__struct = None
+ self.__methods = {}
+ self.__members = {}
+ for cls in self.__mro__:
+ for k, v in cls.__dict__.iteritems():
+ if isinstance(v, FunctionType):
+ self.__methods[k] = Method(v, self)
+ if isinstance(v, member):
+ self.__members[k] = v
+ assert self.__struct is not None
+ v.register(k, self.__struct)
+ self.analyze_slots()
+
+ def analyze_slots(self):
+ self.__slots = {}
+ for s in Slots:
+ if s.special is not None:
+ meth = self.__methods.get(s.special)
+ if meth is not None:
+ self.__slots[s] = meth
+ self.__slots[TP_NAME] = '"%s.%s"' % (self.__module, self.__name__)
+ if self.__doc__:
+ self.__slots[TP_DOC] = "%s_doc" % self.name
+ if self.__struct is not None:
+ self.__slots[TP_BASICSIZE] = "sizeof(%s)" % self.__struct.name
+ self.__slots[TP_DEALLOC] = "%s_dealloc" % self.name
+ if self.__methods:
+ self.__slots[TP_METHODS] = "%s_methods" % self.name
+ if self.__members:
+ self.__slots[TP_MEMBERS] = "%s_members" % self.name
+
+ def initvars(self):
+ v = self.__vars = {}
+ v["TypeName"] = self.__name__
+ v["CTypeName"] = "Py%s_Type" % self.__name__
+ v["MethodDefName"] = self.__slots[TP_METHODS]
+ if self.__doc__:
+ v["DocstringVar"] = self.__slots[TP_DOC]
+ v["Docstring"] = cstring(unindent(self.__doc__))
+ if self.__struct is not None:
+ v["StructName"] = self.__struct.name
+ if self.__members:
+ v["MemberDefName"] = self.__slots[TP_MEMBERS]
+
+ def dump_memberdef(self, f):
+ def p(templ, vars=self.__vars):
+ print >> f, templ % vars
+
+ if not self.__members:
+ return
+ p(template.memberdef_start)
+ for name, slot in sortitems(self.__members):
+ slot.dump(f)
+ p(template.memberdef_end)
+
+ def dump_slots(self, f):
+ def p(templ, vars=self.__vars):
+ print >> f, templ % vars
+
+ if self.struct:
+ p(template.dealloc_func, {"name" : self.__slots[TP_DEALLOC]})
+
+ p(template.type_struct_start)
+ for s in Slots[:-5]: # XXX
+ val = self.__slots.get(s, s.default)
+ ntabs = 4 - (4 + len(val)) / 8
+ line = " %s,%s/* %s */" % (val, "\t" * ntabs, s.name)
+ print >> f, line
+ p(template.type_struct_end)
+
+ def dump_init(self, f):
+ def p(templ):
+ print >> f, templ % self.__vars
+
+ p(template.type_init_type)
+ p(template.module_add_type)
+
+class Type:
+ __metaclass__ = TypeMetaclass
+
diff --git a/Tools/framer/framer/function.py b/Tools/framer/framer/function.py
new file mode 100644
index 0000000..595cc8d
--- /dev/null
+++ b/Tools/framer/framer/function.py
@@ -0,0 +1,173 @@
+"""Functions."""
+
+from framer import template
+from framer.util import cstring, unindent
+
+METH_O = "METH_O"
+METH_NOARGS = "METH_NOARGS"
+METH_VARARGS = "METH_VARARGS"
+
+def parsefmt(fmt):
+ for c in fmt:
+ if c == '|':
+ continue
+ yield c
+
+class Argument:
+
+ def __init__(self, name):
+ self.name = name
+ self.ctype = "PyObject *"
+ self.default = None
+
+ def __str__(self):
+ return "%s%s" % (self.ctype, self.name)
+
+ def setfmt(self, code):
+ self.ctype = self._codes[code]
+ if self.ctype[-1] != "*":
+ self.ctype += " "
+
+ _codes = {"O": "PyObject *",
+ "i": "int",
+ }
+
+ def decl(self):
+ if self.default is None:
+ return str(self) + ";"
+ else:
+ return "%s = %s;" % (self, self.default)
+
+class _ArgumentList(object):
+
+ # these instance variables should be initialized by subclasses
+ ml_meth = None
+ fmt = None
+
+ def __init__(self, args):
+ self.args = map(Argument, args)
+
+ def __len__(self):
+ return len(self.args)
+
+ def __getitem__(self, i):
+ return self.args[i]
+
+ def dump_decls(self, f):
+ pass
+
+class NoArgs(_ArgumentList):
+
+ def __init__(self, args):
+ assert len(args) == 0
+ super(NoArgs, self).__init__(args)
+ self.ml_meth = METH_NOARGS
+
+ def c_args(self):
+ return "PyObject *self"
+
+class OneArg(_ArgumentList):
+
+ def __init__(self, args):
+ assert len(args) == 1
+ super(OneArg, self).__init__(args)
+ self.ml_meth = METH_O
+
+ def c_args(self):
+ return "PyObject *self, %s" % self.args[0]
+
+class VarArgs(_ArgumentList):
+
+ def __init__(self, args, fmt=None):
+ super(VarArgs, self).__init__(args)
+ self.ml_meth = METH_VARARGS
+ if fmt is not None:
+ self.fmt = fmt
+ i = 0
+ for code in parsefmt(fmt):
+ self.args[i].setfmt(code)
+ i += 1
+
+ def c_args(self):
+ return "PyObject *self, PyObject *args"
+
+ def targets(self):
+ return ", ".join(["&%s" % a.name for a in self.args])
+
+ def dump_decls(self, f):
+ for a in self.args:
+ print >> f, " %s" % a.decl()
+
+def ArgumentList(func, method):
+ code = func.func_code
+ args = code.co_varnames[:code.co_argcount]
+ if method:
+ args = args[1:]
+ pyarg = getattr(func, "pyarg", None)
+ if pyarg is not None:
+ args = VarArgs(args, pyarg)
+ if func.func_defaults:
+ L = list(func.func_defaults)
+ ndefault = len(L)
+ i = len(args) - ndefault
+ while L:
+ args[i].default = L.pop(0)
+ return args
+ else:
+ if len(args) == 0:
+ return NoArgs(args)
+ elif len(args) == 1:
+ return OneArg(args)
+ else:
+ return VarArgs(args)
+
+class Function:
+
+ method = False
+
+ def __init__(self, func, parent):
+ self._func = func
+ self._parent = parent
+ self.analyze()
+ self.initvars()
+
+ def dump(self, f):
+ def p(templ, vars=None): # helper function to generate output
+ if vars is None:
+ vars = self.vars
+ print >> f, templ % vars
+
+ if self.__doc__:
+ p(template.docstring)
+
+ d = {"name" : self.vars["CName"],
+ "args" : self.args.c_args(),
+ }
+ p(template.funcdef_start, d)
+
+ self.args.dump_decls(f)
+
+ if self.args.ml_meth == METH_VARARGS:
+ p(template.varargs)
+
+ p(template.funcdef_end)
+
+ def analyze(self):
+ self.__doc__ = self._func.__doc__
+ self.args = ArgumentList(self._func, self.method)
+
+ def initvars(self):
+ v = self.vars = {}
+ v["PythonName"] = self._func.__name__
+ s = v["CName"] = "%s_%s" % (self._parent.name, self._func.__name__)
+ v["DocstringVar"] = s + "_doc"
+ v["MethType"] = self.args.ml_meth
+ if self.__doc__:
+ v["Docstring"] = cstring(unindent(self.__doc__))
+ if self.args.fmt is not None:
+ v["ArgParse"] = self.args.fmt
+ v["ArgTargets"] = self.args.targets()
+
+class Method(Function):
+
+ method = True
diff --git a/Tools/framer/framer/member.py b/Tools/framer/framer/member.py
new file mode 100644
index 0000000..5faf462
--- /dev/null
+++ b/Tools/framer/framer/member.py
@@ -0,0 +1,73 @@
+from framer import template
+from framer.util import cstring, unindent
+
+T_SHORT = "T_SHORT"
+T_INT = "T_INT"
+T_LONG = "T_LONG"
+T_FLOAT = "T_FLOAT"
+T_DOUBLE = "T_DOUBLE"
+T_STRING = "T_STRING"
+T_OBJECT = "T_OBJECT"
+T_CHAR = "T_CHAR"
+T_BYTE = "T_BYTE"
+T_UBYTE = "T_UBYTE"
+T_UINT = "T_UINT"
+T_ULONG = "T_ULONG"
+T_STRING_INPLACE = "T_STRING_INPLACE"
+T_OBJECT_EX = "T_OBJECT_EX"
+
+RO = READONLY = "READONLY"
+READ_RESTRICTED = "READ_RESTRICTED"
+WRITE_RESTRICTED = "WRITE_RESTRICTED"
+RESTRICT = "RESTRICTED"
+
+c2t = {"int" : T_INT,
+ "unsigned int" : T_UINT,
+ "long" : T_LONG,
+ "unsigned long" : T_LONG,
+ "float" : T_FLOAT,
+ "double" : T_DOUBLE,
+ "char *" : T_CHAR,
+ "PyObject *" : T_OBJECT,
+ }
+
+class member(object):
+
+ def __init__(self, cname=None, type=None, flags=None, doc=None):
+ self.type = type
+ self.flags = flags
+ self.cname = cname
+ self.doc = doc
+ self.name = None
+ self.struct = None
+
+ def register(self, name, struct):
+ self.name = name
+ self.struct = struct
+ self.initvars()
+
+ def initvars(self):
+ v = self.vars = {}
+ v["PythonName"] = self.name
+ if self.cname is not None:
+ v["CName"] = self.cname
+ else:
+ v["CName"] = self.name
+ v["Flags"] = self.flags or "0"
+ v["Type"] = self.get_type()
+ if self.doc is not None:
+ v["Docstring"] = cstring(unindent(self.doc))
+ v["StructName"] = self.struct.name
+
+ def get_type(self):
+ """Deduce type code from struct specification if not defined"""
+ if self.type is not None:
+ return self.type
+ ctype = self.struct.get_type(self.name)
+ return c2t[ctype]
+
+ def dump(self, f):
+ if self.doc is None:
+ print >> f, template.memberdef_def % self.vars
+ else:
+ print >> f, template.memberdef_def_doc % self.vars
diff --git a/Tools/framer/framer/slots.py b/Tools/framer/framer/slots.py
new file mode 100644
index 0000000..d369c9a
--- /dev/null
+++ b/Tools/framer/framer/slots.py
@@ -0,0 +1,64 @@
+"""Descriptions of all the slots in Python's type objects."""
+
+class Slot(object):
+ def __init__(self, name, cast=None, special=None, default="0"):
+ self.name = name
+ self.cast = cast
+ self.special = special
+ self.default = default
+
+Slots = (Slot("ob_size"),
+ Slot("tp_name"),
+ Slot("tp_basicsize"),
+ Slot("tp_itemsize"),
+ Slot("tp_dealloc", "destructor"),
+ Slot("tp_print", "printfunc"),
+ Slot("tp_getattr", "getattrfunc"),
+ Slot("tp_setattr", "setattrfunc"),
+ Slot("tp_compare", "cmpfunc", "__cmp__"),
+ Slot("tp_repr", "reprfunc", "__repr__"),
+ Slot("tp_as_number"),
+ Slot("tp_as_sequence"),
+ Slot("tp_as_mapping"),
+ Slot("tp_hash", "hashfunc", "__hash__"),
+ Slot("tp_call", "ternaryfunc", "__call__"),
+ Slot("tp_str", "reprfunc", "__str__"),
+ Slot("tp_getattro", "getattrofunc", "__getattr__", # XXX
+ "PyObject_GenericGetAttr"),
+ Slot("tp_setattro", "setattrofunc", "__setattr__"),
+ Slot("tp_as_buffer"),
+ Slot("tp_flags", default="Py_TPFLAGS_DEFAULT"),
+ Slot("tp_doc"),
+ Slot("tp_traverse", "traverseprox"),
+ Slot("tp_clear", "inquiry"),
+ Slot("tp_richcompare", "richcmpfunc"),
+ Slot("tp_weaklistoffset"),
+ Slot("tp_iter", "getiterfunc", "__iter__"),
+ Slot("tp_iternext", "iternextfunc", "__next__"), # XXX
+ Slot("tp_methods"),
+ Slot("tp_members"),
+ Slot("tp_getset"),
+ Slot("tp_base"),
+ Slot("tp_dict"),
+ Slot("tp_descr_get", "descrgetfunc"),
+ Slot("tp_descr_set", "descrsetfunc"),
+ Slot("tp_dictoffset"),
+ Slot("tp_init", "initproc", "__init__"),
+ Slot("tp_alloc", "allocfunc"),
+ Slot("tp_new", "newfunc"),
+ Slot("tp_free", "freefunc"),
+ Slot("tp_is_gc", "inquiry"),
+ Slot("tp_bases"),
+ Slot("tp_mro"),
+ Slot("tp_cache"),
+ Slot("tp_subclasses"),
+ Slot("tp_weaklist"),
+ )
+
+# give some slots symbolic names
+TP_NAME = Slots[1]
+TP_BASICSIZE = Slots[2]
+TP_DEALLOC = Slots[4]
+TP_DOC = Slots[20]
+TP_METHODS = Slots[27]
+TP_MEMBERS = Slots[28]
diff --git a/Tools/framer/framer/struct.py b/Tools/framer/framer/struct.py
new file mode 100644
index 0000000..3948740
--- /dev/null
+++ b/Tools/framer/framer/struct.py
@@ -0,0 +1,52 @@
+"""Rudimentary parser for C struct definitions."""
+
+import re
+
+PyObject_HEAD = "PyObject_HEAD"
+PyObject_VAR_HEAD = "PyObject_VAR_HEAD"
+
+rx_name = re.compile("} (\w+);")
+
+class Struct:
+ def __init__(self, name, head, members):
+ self.name = name
+ self.head = head
+ self.members = members
+
+ def get_type(self, name):
+ for _name, type in self.members:
+ if name == _name:
+ return type
+ raise ValueError, "no member named %s" % name
+
+def parse(s):
+ """Parse a C struct definition.
+
+ The parser is very restricted in what it will accept.
+ """
+
+ lines = filter(None, s.split("\n")) # get non-empty lines
+ assert lines[0].strip() == "typedef struct {"
+ pyhead = lines[1].strip()
+ assert (pyhead.startswith("PyObject") and
+ pyhead.endswith("HEAD"))
+ members = []
+ for line in lines[2:]:
+ line = line.strip()
+ if line.startswith("}"):
+ break
+
+ assert line.endswith(";")
+ line = line[:-1]
+ words = line.split()
+ name = words[-1]
+ type = " ".join(words[:-1])
+ if name[0] == "*":
+ name = name[1:]
+ type += " *"
+ members.append((name, type))
+ name = None
+ mo = rx_name.search(line)
+ assert mo is not None
+ name = mo.group(1)
+ return Struct(name, pyhead, members)
diff --git a/Tools/framer/framer/structparse.py b/Tools/framer/framer/structparse.py
new file mode 100644
index 0000000..419228a
--- /dev/null
+++ b/Tools/framer/framer/structparse.py
@@ -0,0 +1,46 @@
+"""Rudimentary parser for C struct definitions."""
+
+import re
+
+PyObject_HEAD = "PyObject_HEAD"
+PyObject_VAR_HEAD = "PyObject_VAR_HEAD"
+
+rx_name = re.compile("} (\w+);")
+
+class Struct:
+ def __init__(self, name, head, members):
+ self.name = name
+ self.head = head
+ self.members = members
+
+def parse(s):
+ """Parse a C struct definition.
+
+ The parser is very restricted in what it will accept.
+ """
+
+ lines = filter(None, s.split("\n")) # get non-empty lines
+ assert lines[0].strip() == "typedef struct {"
+ pyhead = lines[1].strip()
+ assert (pyhead.startswith("PyObject") and
+ pyhead.endswith("HEAD"))
+ members = []
+ for line in lines[2:]:
+ line = line.strip()
+ if line.startswith("}"):
+ break
+
+ assert line.endswith(";")
+ line = line[:-1]
+ words = line.split()
+ name = words[-1]
+ type = " ".join(words[:-1])
+ if name[0] == "*":
+ name = name[1:]
+ type += " *"
+ members.append((name, type))
+ name = None
+ mo = rx_name.search(line)
+ assert mo is not None
+ name = mo.group(1)
+ return Struct(name, pyhead, members)
diff --git a/Tools/framer/framer/template.py b/Tools/framer/framer/template.py
new file mode 100644
index 0000000..70e2591
--- /dev/null
+++ b/Tools/framer/framer/template.py
@@ -0,0 +1,102 @@
+"""framer's C code templates.
+
+Templates use the following variables:
+
+FileName: name of the file that contains the C source code
+ModuleName: name of the module, as in "import ModuleName"
+ModuleDocstring: C string containing the module doc string
+"""
+
+module_start = '#include "Python.h"'
+member_include = '#include "structmember.h"'
+
+module_doc = """\
+PyDoc_STRVAR(%(ModuleName)s_doc,
+%(ModuleDocstring)s);
+"""
+
+methoddef_start = """\
+static struct PyMethodDef %(MethodDefName)s[] = {"""
+
+methoddef_def = """\
+ {"%(PythonName)s", (PyCFunction)%(CName)s, %(MethType)s},"""
+
+methoddef_def_doc = """\
+ {"%(PythonName)s", (PyCFunction)%(CName)s, %(MethType)s,
+ %(DocstringVar)s},"""
+
+methoddef_end = """\
+ {NULL, NULL}
+};
+"""
+
+memberdef_start = """\
+#define OFF(X) offsetof(%(StructName)s, X)
+
+static struct PyMemberDef %(MemberDefName)s[] = {"""
+
+memberdef_def_doc = """\
+ {"%(PythonName)s", %(Type)s, OFF(%(CName)s), %(Flags)s,
+ %(Docstring)s},"""
+
+memberdef_def = """\
+ {"%(PythonName)s", %(Type)s, OFF(%(CName)s), %(Flags)s},"""
+
+memberdef_end = """\
+ {NULL}
+};
+
+#undef OFF
+"""
+
+dealloc_func = """static void
+%(name)s(PyObject *ob)
+{
+}
+"""
+
+docstring = """\
+PyDoc_STRVAR(%(DocstringVar)s,
+%(Docstring)s);
+"""
+
+funcdef_start = """\
+static PyObject *
+%(name)s(%(args)s)
+{"""
+
+funcdef_end = """\
+}
+"""
+
+varargs = """\
+ if (!PyArg_ParseTuple(args, \"%(ArgParse)s:%(PythonName)s\",
+ %(ArgTargets)s))
+ return NULL;"""
+
+module_init_start = """\
+PyMODINIT_FUNC
+init%(ModuleName)s(void)
+{
+ PyObject *mod;
+
+ mod = Py_InitModule3("%(ModuleName)s", %(MethodDefName)s,
+ %(ModuleName)s_doc);
+ if (mod == NULL)
+ return;
+"""
+
+type_init_type = " %(CTypeName)s.ob_type = &PyType_Type;"
+module_add_type = """\
+ if (!PyObject_SetAttrString(mod, "%(TypeName)s",
+ (PyObject *)&%(CTypeName)s))
+ return;
+"""
+
+type_struct_start = """\
+static PyTypeObject %(CTypeName)s = {
+ PyObject_HEAD_INIT(0)"""
+
+type_struct_end = """\
+};
+"""
diff --git a/Tools/framer/framer/util.py b/Tools/framer/framer/util.py
new file mode 100644
index 0000000..73f3309
--- /dev/null
+++ b/Tools/framer/framer/util.py
@@ -0,0 +1,35 @@
+def cstring(s, width=70):
+ """Return C string representation of a Python string.
+
+ width specifies the maximum width of any line of the C string.
+ """
+ L = []
+ for l in s.split("\n"):
+ if len(l) < width:
+ L.append(r'"%s\n"' % l)
+
+ return "\n".join(L)
+
+def unindent(s, skipfirst=True):
+ """Return an unindented version of a docstring.
+
+ Removes indentation on lines following the first one, using the
+ leading whitespace of the first indented line that is not blank
+ to determine the indentation.
+ """
+
+ lines = s.split("\n")
+ if skipfirst:
+ first = lines.pop(0)
+ L = [first]
+ else:
+ L = []
+ indent = None
+ for l in lines:
+ ls = l.strip()
+ if ls:
+ indent = len(l) - len(ls)
+ break
+ L += [l[indent:] for l in lines]
+
+ return "\n".join(L)