Initial prototype of framer: a tool to build the frame for extension modules.

This commit is contained in:
Jeremy Hylton 2002-08-05 18:29:45 +00:00
parent f4d32df19d
commit 53d527ad18
12 changed files with 915 additions and 0 deletions

8
Tools/framer/README.txt Normal file
View File

@ -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.

6
Tools/framer/TODO.txt Normal file
View File

@ -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.

127
Tools/framer/example.py Normal file
View File

@ -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()

View File

@ -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.
"""

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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]

View File

@ -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)

View File

@ -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)

View File

@ -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 = """\
};
"""

View File

@ -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)