diff options
-rw-r--r-- | Tools/bgen/bgen/bgen.py | 7 | ||||
-rw-r--r-- | Tools/bgen/bgen/bgenGenerator.py | 173 | ||||
-rw-r--r-- | Tools/bgen/bgen/bgenGeneratorGroup.py | 35 | ||||
-rw-r--r-- | Tools/bgen/bgen/bgenModule.py | 69 | ||||
-rw-r--r-- | Tools/bgen/bgen/bgenObjectDefinition.py | 130 | ||||
-rw-r--r-- | Tools/bgen/bgen/bgenOutput.py | 129 | ||||
-rw-r--r-- | Tools/bgen/bgen/bgenType.py | 362 |
7 files changed, 905 insertions, 0 deletions
diff --git a/Tools/bgen/bgen/bgen.py b/Tools/bgen/bgen/bgen.py new file mode 100644 index 0000000..aab35de --- /dev/null +++ b/Tools/bgen/bgen/bgen.py @@ -0,0 +1,7 @@ +"Export everything in the various bgen submodules." + +from bgenType import * +from bgenOutput import * +from bgenGenerator import * +from bgenModule import * +from bgenObjectDefinition import * diff --git a/Tools/bgen/bgen/bgenGenerator.py b/Tools/bgen/bgen/bgenGenerator.py new file mode 100644 index 0000000..ce38763 --- /dev/null +++ b/Tools/bgen/bgen/bgenGenerator.py @@ -0,0 +1,173 @@ +from bgenOutput import * +from bgenType import * + + +Error = "bgenGenerator.Error" + + +# Strings to specify argument transfer modes in generator calls +IN = "in" +OUT = "out" +INOUT = IN_OUT = "in-out" + + +class Generator: + + # XXX There are some funny things with the argument list. + # XXX It would be better to get rid of this and require + # XXX each argument to be a type object or a tuple of the form + # XXX (inoutmode, typeobject, argumentname) + # XXX or possibly even a Variable() instance... + + def __init__(self, *argumentList): + apply(self.parseArguments, argumentList) + self.prefix = "XXX" # Will be changed by setprefix() call + self.objecttype = "object" # Type of _self argument to function + self.itselftype = None # Type of _self->ob_itself, if defined + + def setprefix(self, prefix): + self.prefix = prefix + + def setselftype(self, selftype, itselftype): + self.objecttype = selftype + self.itselftype = itselftype + + def parseArguments(self, returnType, name, *argumentList): + if returnType: + self.rv = Variable(returnType, "_rv", OutMode) + else: + self.rv = None + self.name = name + self.argumentList = [] + if self.rv: + self.argumentList.append(rv) + self.parseArgumentList(argumentList) + + def parseArgumentList(self, argumentList): + from types import * + iarg = 0 + for arg in argumentList: + # Arguments can either be: + # - a type + # - a tuple (type, [name, [mode]]) + # - a variable + iarg = iarg + 1 + if hasattr(arg, 'typeName'): + arg = Variable(arg) + elif type(arg) == TupleType: + arg = apply(Variable, arg) + if arg.name is None: + arg.name = "_arg%d" % iarg + self.argumentList.append(arg) + + def reference(self, name = None): + if name is None: + name = self.name + Output("{\"%s\", (PyCFunction)%s_%s, 1},", + name, self.prefix, self.name) + + def generate(self): + self.functionheader() + self.declarations() + self.getargs() + self.callit() + self.checkit() + self.returnvalue() + self.functiontrailer() + + def functionheader(self): + Output() + Output("static PyObject *%s_%s(_self, _args)", + self.prefix, self.name) + IndentLevel() + Output("%s *_self;", self.objecttype) + Output("PyObject *_args;") + DedentLevel() + OutLbrace() + + def declarations(self): + for arg in self.argumentList: + arg.declare() + + def getargs(self): + fmt = "" + lst = "" + for arg in self.argumentList: + if arg.flags == SelfMode: + continue + if arg.mode in (InMode, InOutMode): + fmt = fmt + arg.getargsFormat() + lst = lst + ", " + arg.getargsArgs() + Output("if (!PyArg_ParseTuple(_args, \"%s\"%s))", fmt, lst) + IndentLevel() + Output("return NULL;") + DedentLevel() + for arg in self.argumentList: + if arg.flags == SelfMode: + continue + if arg.mode in (InMode, InOutMode): + arg.getargsCheck() + + def callit(self): + args = "" + for arg in self.argumentList: + if arg is self.rv: + continue + s = arg.passArgument() + if args: s = ", " + s + args = args + s + if self.rv: + Output("%s = %s(%s);", + self.rv.name, self.name, args) + else: + Output("%s(%s);", self.name, args) + + def checkit(self): + for arg in self.argumentList: + arg.errorCheck() + + def returnvalue(self): + fmt = "" + lst = "" + for arg in self.argumentList: + if not arg: continue + if arg.flags == ErrorMode: continue + if arg.mode in (OutMode, InOutMode): + fmt = fmt + arg.mkvalueFormat() + lst = lst + ", " + arg.mkvalueArgs() + if fmt == "": + Output("Py_INCREF(Py_None);") + Output("return Py_None;"); + else: + Output("return Py_BuildValue(\"%s\"%s);", fmt, lst) + + def functiontrailer(self): + OutRbrace() + + +class ManualGenerator(Generator): + + def __init__(self, name, body): + self.name = name + self.body = body + + def definition(self): + self.functionheader() + Output("%s", self.body) + self.functiontrailer() + + +def _test(): + void = None + eggs = Generator(void, "eggs", + Variable(stringptr, 'cmd'), + Variable(int, 'x'), + Variable(double, 'y', InOutMode), + Variable(int, 'status', ErrorMode), + ) + eggs.setprefix("spam") + print "/* START */" + eggs.generate() + +if __name__ == "__main__": + _test() diff --git a/Tools/bgen/bgen/bgenGeneratorGroup.py b/Tools/bgen/bgen/bgenGeneratorGroup.py new file mode 100644 index 0000000..b8cd72e --- /dev/null +++ b/Tools/bgen/bgen/bgenGeneratorGroup.py @@ -0,0 +1,35 @@ +from bgenOutput import * + +class GeneratorGroup: + + def __init__(self, prefix): + self.prefix = prefix + self.generators = [] + + def add(self, g): + g.setprefix(self.prefix) + self.generators.append(g) + + def generate(self): + for g in self.generators: + g.generate() + Output() + Output("static struct methodlist %s_methods[] = {", self.prefix) + IndentLevel() + for g in self.generators: + g.reference() + Output("{NULL, NULL, 0}") + DedentLevel() + Output("};") + + +def _test(): + from bgenGenerator import Generator + group = GeneratorGroup("spam") + eggs = Generator(void, "eggs") + group.add(eggs) + print "/* START */" + group.generate() + +if __name__ == "__main__": + _test() diff --git a/Tools/bgen/bgen/bgenModule.py b/Tools/bgen/bgen/bgenModule.py new file mode 100644 index 0000000..8f23004 --- /dev/null +++ b/Tools/bgen/bgen/bgenModule.py @@ -0,0 +1,69 @@ +from bgenOutput import * +from bgenGeneratorGroup import GeneratorGroup + +class Module(GeneratorGroup): + + def __init__(self, name, prefix = None, + includestuff = None, + initstuff = None): + GeneratorGroup.__init__(self, prefix or name) + self.name = name + self.includestuff = includestuff + self.initstuff = initstuff + + def addobject(self, od): + self.generators.append(od) + + def generate(self): + OutHeader1("Module " + self.name) + Output("#include <Python.h>") + Output() + + if self.includestuff: + Output() + Output("%s", self.includestuff) + + self.declareModuleVariables() + + GeneratorGroup.generate(self) + + Output() + Output("void init%s()", self.name) + OutLbrace() + Output("object *m;") + Output("object *d;") + Output() + + if self.initstuff: + Output("%s", self.initstuff) + Output() + + Output("m = initmodule(\"%s\", %s_methods);", + self.name, self.prefix) + Output("d = getmoduledict(m);") + self.createModuleVariables() + OutRbrace() + OutHeader1("End module " + self.name) + + def declareModuleVariables(self): + self.errorname = self.prefix + "_Error" + Output("static object *%s;", self.errorname) + + def createModuleVariables(self): + Output("""if ((%s = newstringobject("%s.Error")) == NULL ||""", + self.errorname, self.name) + Output(""" dictinsert(d, "Error", %s) != 0)""", + self.errorname) + Output("""\tfatal("can't initialize %s.Error");""", + self.name) + + +def _test(): + from bgenGenerator import Generator + m = Module("spam", "", "#include <stdio.h>") + g = Generator(None, "bacon") + m.add(g) + m.generate() + +if __name__ == "__main__": + _test() diff --git a/Tools/bgen/bgen/bgenObjectDefinition.py b/Tools/bgen/bgen/bgenObjectDefinition.py new file mode 100644 index 0000000..6573fd4 --- /dev/null +++ b/Tools/bgen/bgen/bgenObjectDefinition.py @@ -0,0 +1,130 @@ +from bgenOutput import * +from bgenGeneratorGroup import GeneratorGroup + +class ObjectDefinition(GeneratorGroup): + + def __init__(self, name, prefix, itselftype): + import string + GeneratorGroup.__init__(self, prefix or name) + self.name = name + self.itselftype = itselftype + self.objecttype = name + 'Object' + self.typename = self.prefix + '_' + \ + string.upper(name[:1]) + \ + string.lower(name[1:]) + '_Type' + + def add(self, g): + g.setselftype(self.objecttype, self.itselftype) + GeneratorGroup.add(self, g) + + def reference(self): + # In case we are referenced from a module + pass + + def generate(self): + # XXX This should use long strings and %(varname)s substitution! + + OutHeader2("Object type " + self.name) + + Output("staticforward PyTypeObject %s;", self.typename) + Output() + + Output("#define is_%sobject(x) ((x)->ob_type == %s)", + self.name, self.typename) + Output() + + Output("typedef struct {") + IndentLevel() + Output("OB_HEAD") + Output("%s ob_itself;", self.itselftype) + DedentLevel() + Output("} %s;", self.objecttype) + Output() + + Output("static %s *new%s(itself)", self.objecttype, self.objecttype) + IndentLevel() + Output("%s itself;", self.itselftype) + DedentLevel() + OutLbrace() + Output("%s *it;", self.objecttype) + Output("it = PyObject_NEW(%s, &%s);", self.objecttype, self.typename) + Output("if (it == NULL) return NULL;") + Output("it->ob_itself = itself;") + Output("return it;") + OutRbrace() + Output() + + Output("static void %s_dealloc(self)", self.prefix) + IndentLevel() + Output("%s *self;", self.objecttype) + DedentLevel() + OutLbrace() +## Output("if (self->ob_itself != NULL)") +## OutLbrace() +## self.outputFreeIt("self->ob_itself") +## OutRbrace() + Output("DEL(self);") + OutRbrace() + Output() + +## Output("static int %s_converter(p_itself, p_it)", self.prefix) +## IndentLevel() +## Output("%s *p_itself;", self.itselftype) +## Output("%s **p_it;", self.objecttype) +## DedentLevel() +## OutLbrace() +## Output("if (**p_it == NULL)") +## OutLbrace() +## Output("*p_it = new%s(*p_itself);", self.objecttype) +## OutRbrace() +## Output("else") +## OutLbrace() +## Output("*p_itself = (*p_it)->ob_itself;") +## OutRbrace() +## Output("return 1;") +## OutRbrace() +## Output() + + GeneratorGroup.generate(self) + + self.outputGetattr() + + self.outputSetattr() + + Output("static PyTypeObject %s = {", self.typename) + IndentLevel() + Output("PyObject_HEAD_INIT(&PyType_Type)") + Output("0, /*ob_size*/") + Output("\"%s\", /*tp_name*/", self.name) + Output("sizeof(%s), /*tp_basicsize*/", self.objecttype) + Output("0, /*tp_itemsize*/") + Output("/* methods */") + Output("(destructor) %s_dealloc, /*tp_dealloc*/", self.prefix) + Output("0, /*tp_print*/") + Output("(getattrfunc) %s_getattr, /*tp_getattr*/", self.prefix) + Output("(setattrfunc) %s_setattr, /*tp_setattr*/", self.prefix) + DedentLevel() + Output("};") + + OutHeader2("End object type " + self.name) + + def outputFreeIt(self, name): + Output("DEL(%s); /* XXX */", name) + + def outputGetattr(self): + Output("static PyObject *%s_getattr(self, name)", self.prefix) + IndentLevel() + Output("%s *self;", self.objecttype) + Output("char *name;") + DedentLevel() + OutLbrace() + self.outputGetattrBody() + OutRbrace() + Output() + + def outputGetattrBody(self): + Output("return findmethod(self, %s_methods, name);", self.prefix) + + def outputSetattr(self): + Output("#define %s_setattr NULL", self.prefix) + Output() diff --git a/Tools/bgen/bgen/bgenOutput.py b/Tools/bgen/bgen/bgenOutput.py new file mode 100644 index 0000000..06ca3ed --- /dev/null +++ b/Tools/bgen/bgen/bgenOutput.py @@ -0,0 +1,129 @@ +"""Output primitives for the binding generator classes. + +This should really be a class, but then everybody would be passing +the output object to each other. I chose for the simpler approach +of a module with a global variable. Use SetOutputFile() to change +the output file. +""" + +def SetOutputFile(file = None): + """Call this with an open file object to make that the output file. + + Call it without arguments to reset the output file to sys.stdout. + """ + global _File + if file is None: + import sys + file = sys.stdout + _File = file + +SetOutputFile() # Initialize _File + +_Level = 0 # Indentation level + +def GetLevel(): + """"Return the current indentation level.""" + return _Level + +def SetLevel(level): + """Set the current indentation level. + + This does no type or range checking -- use at own risk. + """ + global _Level + _Level = level + +def Output(format = "", *args): + """Call this with a format string and arguments for the format. + + A newline is always added. Each line in the output is indented + to the proper indentation level -- even if the result of the + format expansion contains embedded newlines. Exception: lines + beginning with '#' are not indented -- these are assumed to be + C preprprocessor lines. + """ + text = format % args + if _Level > 0: + indent = '\t' * _Level + import string + lines = string.splitfields(text, '\n') + for i in range(len(lines)): + if lines[i] and lines[i][0] != '#': + lines[i] = indent + lines[i] + text = string.joinfields(lines, '\n') + _File.write(text + '\n') + +def IndentLevel(by = 1): + """Increment the indentation level by one. + + When called with an argument, adds it to the indentation level. + """ + global _Level + if _Level+by < 0: + raise Error, "indentation underflow (internal error)" + _Level = _Level + by + +def DedentLevel(by = 1): + """Decfrement the indentation level by one. + + When called with an argument, subtracts it from the indentation level. + """ + IndentLevel(-by) + +def OutLbrace(): + """Output a '{' on a line by itself and increase the indentation level.""" + Output("{") + IndentLevel() + +def OutRbrace(): + """Decrease the indentation level and output a '}' on a line by itself.""" + DedentLevel() + Output("}") + +def OutHeader(text, dash): + """Output a header comment using a given dash character.""" + n = 64 - len(text) + Output() + Output("/* %s %s %s */", dash * (n/2), text, dash * (n - n/2)) + Output() + +def OutHeader1(text): + """Output a level 1 header comment (uses '=' dashes).""" + OutHeader(text, "=") + +def OutHeader2(text): + """Output a level 2 header comment (uses '-' dashes).""" + OutHeader(text, "-") + + +def _test(): + """Test program. Run when the module is run as a script.""" + OutHeader1("test bgenOutput") + Output(""" +#include <Python.h> +#include <stdio.h> +""") + Output("main(argc, argv)") + IndentLevel() + Output("int argc;") + Output("char **argv;") + DedentLevel() + OutLbrace() + Output("int i;") + Output() + Output("""\ +/* Here are a few comment lines. + Just to test indenting multiple lines. + + End of the comment lines. */ +""") + Output("for (i = 0; i < argc; i++)") + OutLbrace() + Output('printf("argv[%%d] = %%s\\n", i, argv[i]);') + OutRbrace() + Output("exit(0)") + OutRbrace() + OutHeader2("end test") + +if __name__ == '__main__': + _test() diff --git a/Tools/bgen/bgen/bgenType.py b/Tools/bgen/bgen/bgenType.py new file mode 100644 index 0000000..66f78eb --- /dev/null +++ b/Tools/bgen/bgen/bgenType.py @@ -0,0 +1,362 @@ +"""Type and Variable classes and a modest collection of standard types.""" + +from bgenOutput import * + + +# Values to represent argument transfer modes +InMode = 1 # input-only argument +OutMode = 2 # output-only argument +InOutMode = 3 # input-output argument +ModeMask = 3 # bits to keep for mode + + +# Special cases for mode/flags argument +# XXX This is still a mess! +SelfMode = 4+InMode # this is 'self' -- don't declare it +ReturnMode = 8+OutMode # this is the function return value +ErrorMode = 16+OutMode # this is an error status -- turn it into an exception + + +class Type: + + """Define the various things you can do with a C type. + + Most methods are intended to be extended or overridden. + """ + + def __init__(self, typeName, fmt): + """Call with the C name and getargs format for the type. + + Example: int = Type("int", "i") + """ + self.typeName = typeName + self.fmt = fmt + + def declare(self, name): + """Declare a variable of the type with a given name. + + Example: int.declare('spam') prints "int spam;" + """ + Output("%s %s;", self.typeName, name) + + def getargsFormat(self): + """Return the format for this type for use with [new]getargs(). + + Example: int.getargsFormat() returns the string "i". + """ + return self.fmt + + def getargsArgs(self, name): + """Return an argument for use with [new]getargs(). + + Example: int.getargsArgs("spam") returns the string "&spam". + """ + return "&" + name + + def getargsCheck(self, name): + """Perform any needed post-[new]getargs() checks. + + This is type-dependent; the default does not check for errors. + An example would be a check for a maximum string length.""" + + def passInput(self, name): + """Return an argument for passing a variable into a call. + + Example: int.passInput("spam") returns the string "spam". + """ + return name + + def passOutput(self, name): + """Return an argument for returning a variable out of a call. + + Example: int.passOutput("spam") returns the string "&spam". + """ + return "&" + name + + def errorCheck(self, name): + """Check for an error returned in the variable. + + This is type-dependent; the default does not check for errors. + An example would be a check for a NULL pointer. + If an error is found, the generated routine should + raise an exception and return NULL. + + XXX There should be a way to add error clean-up code. + """ + Output("/* XXX no err check for %s %s */", self.typeName, name) + + def mkvalueFormat(self): + """Return the format for this type for use with mkvalue(). + + This is normally the same as getargsFormat() but it is + a separate function to allow future divergence. + """ + return self.getargsFormat() + + def mkvalueArgs(self, name): + """Return an argument for use with mkvalue(). + + Example: int.mkvalueArgs("spam") returns the string "spam". + """ + return name + + +# A modest collection of standard C types. +void = None +short = Type("short", "h") +int = Type("int", "i") +long = Type("long", "l") +float = Type("float", "f") +double = Type("double", "d") +stringptr = Type("char*", "s") +char = Type("char", "c") + + +# Some Python related types. +objectptr = Type("object*", "O") +stringobjectptr = Type("stringobject*", "S") +# Etc. + + +# Buffers are character arrays that may contain null bytes. +# Their length is either Fixed or Sized (i.e. given by a separate argument). + +class SizedInputBuffer: + + "Sized input buffer -- buffer size is an input parameter" + + def __init__(self, size): + self.size = size + + def declare(self, name): + Output("char *%s;", name) + Output("int %s__len__;", name) + + def getargsFormat(self): + return "s#" + + def getargsArgs(self, name): + return "%s, %s__len__" % (name, name) + + def getargsCheck(self, name): + pass + + def passInput(self, name): + return "%s, %s__len__" % (name, name) + + +class FixedInputBuffer(SizedInputBuffer): + + "Fixed input buffer -- buffer size is a constant" + + def getargsCheck(self, name): + Output("if (%s__len__ != %s)", name, str(self.size)) + IndentLevel() + Output('err_setstr(TypeError, "bad string length");') + DedentLevel() + + def passInput(self, name): + return name + + +class SizedOutputBuffer: + + "Sized output buffer -- buffer size is an input-output parameter" + + def __init__(self, maxsize): + self.maxsize = maxsize + + def declare(self, name): + Output("char %s[%s];", name, str(self.maxsize)) + Output("int %s__len__ = %s;", name, str(self.maxsize)) + + def passOutput(self, name): + return "%s, &%s__len__" % (name, name) + + def errorCheck(self, name): + pass + + def mkvalueFormat(self): + return "s#" + + def mkvalueArgs(self, name): + return "%s, %s__len__" % (name, name) + + +class FixedOutputBuffer: + + "Fixed output buffer -- buffer size is a constant" + + def __init__(self, size): + self.size = size + + def declare(self, name): + Output("char %s[%s];", name, str(self.size)) + + def passOutput(self, name): + return name + + def errorCheck(self, name): + pass + + def mkvalueFormat(self): + return "s#" + + def mkvalueArgs(self, name): + return "%s, %s" % (name, str(self.size)) + + +# Strings are character arrays terminated by a null byte. +# For input, this is covered by stringptr. +# For output, there are again two variants: Fixed (size is a constant +# given in the documentation) or Sized (size is given by a variable). +# (Note that this doesn't cover output parameters in which a string +# pointer is returned.) + +class SizedOutputString: + + "Null-terminated output string -- buffer size is an input parameter" + + def __init__(self, bufsize): + self.bufsize = bufsize + + def declare(self, name): + Output("char %s[%s];", name, str(self.bufsize)) + + def passOutput(self, name): + return "%s, %s" % (name, str(self.bufsize)) + + def errorCheck(self, name): + pass + + def mkvalueFormat(self): + return "s" + + def mkvalueArgs(self, name): + return name + + +class FixedOutputString(SizedOutputString): + + "Null-terminated output string -- buffer size is a constant" + + def passOutput(self, name): + return name + + +class StructureByValue: + + "Structure passed by value -- mapped to a Python string (for now)" + + def __init__(self, typeName): + self.typeName = typeName + + def declare(self, name): + n = len(self.typeName) + Output("%-*s %s;", n, self.typeName, name) + Output("%-*s*%s__str__;", n, "char", name) + Output("%-*s %s__len__;", n, "int", name) + + def getargsFormat(self): + return "s#" + + def getargsArgs(self, name): + return "&%s__str__, &%s__len__" % (name, name) + + def getargsCheck(self, name): + Output("if (%s__len__ != sizeof %s)", name, name) + IndentLevel() + Output('err_setstr(TypeError, "bad structure length");') + DedentLevel() + Output("memcpy(&%s, %s__str__, %s__len__);", + name, name, name) + + def passInput(self, name): + return "%s" % name + + def passOutput(self, name): + return "&%s" % name + + def errorCheck(self, name): + pass + + def mkvalueFormat(self): + return "s#" + + def mkvalueArgs(self, name): + return "(char *)&%s, (int)sizeof %s" % (name, name) + + +class StructureByAddress(StructureByValue): + + def passInput(self, name): + return "&%s" % name + + +class Variable: + + """A Variable holds a type, a name, a transfer mode and flags. + + Most of its methods call the correponding type method with the + variable name. + """ + + def __init__(self, type, name = None, flags = InMode): + """Call with a type, a name and flags. + + If name is None, it muse be set later. + flags defaults to InMode. + """ + self.type = type + self.name = name + self.flags = flags + self.mode = flags & ModeMask + + def declare(self): + """Declare the variable if necessary. + + If it is "self", it is not declared. + """ + if self.flags != SelfMode: + self.type.declare(self.name) + + def getargsFormat(self): + """Call the type's getargsFormatmethod.""" + return self.type.getargsFormat() + + def getargsArgs(self): + """Call the type's getargsArgsmethod.""" + return self.type.getargsArgs(self.name) + + def getargsCheck(self): + return self.type.getargsCheck(self.name) + + def passArgument(self): + """Return the string required to pass the variable as argument. + + For "in" arguments, return the variable name. + For "out" and "in out" arguments, + return its name prefixed with "&". + """ + if self.mode == InMode: + return self.type.passInput(self.name) + if self.mode in (OutMode, InOutMode): + return self.type.passOutput(self.name) + # XXX Shouldn't get here + return "/*mode?*/" + self.type.passInput(self.name) + + def errorCheck(self): + """Check for an error if necessary. + + This only generates code if the variable's mode is ErrorMode. + """ + if self.flags == ErrorMode: + self.type.errorCheck(self.name) + + def mkvalueFormat (self): + """Call the type's mkvalueFormatmethod.""" + return self.type.mkvalueFormat() + + def mkvalueArgs(self): + """Call the type's mkvalueArgs method.""" + return self.type.mkvalueArgs(self.name) |