From b3928d2ffdc75e621b410bec99aec878f2c20d2f Mon Sep 17 00:00:00 2001 From: Jack Jansen Date: Mon, 17 Feb 1997 16:56:56 +0000 Subject: An initial stab at calling random C routines from Python --- Mac/Modules/calldll.c | 903 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 903 insertions(+) create mode 100644 Mac/Modules/calldll.c diff --git a/Mac/Modules/calldll.c b/Mac/Modules/calldll.c new file mode 100644 index 0000000..02e0162 --- /dev/null +++ b/Mac/Modules/calldll.c @@ -0,0 +1,903 @@ +/*********************************************************** +Copyright 1991-1995 by Stichting Mathematisch Centrum, Amsterdam, +The Netherlands. + + All Rights Reserved + +Permission to use, copy, modify, and distribute this software and its +documentation for any purpose and without fee is hereby granted, +provided that the above copyright notice appear in all copies and that +both that copyright notice and this permission notice appear in +supporting documentation, and that the names of Stichting Mathematisch +Centrum or CWI or Corporation for National Research Initiatives or +CNRI not be used in advertising or publicity pertaining to +distribution of the software without specific, written prior +permission. + +While CWI is the initial source for this software, a modified version +is made available by the Corporation for National Research Initiatives +(CNRI) at the Internet address ftp://ftp.python.org. + +STICHTING MATHEMATISCH CENTRUM AND CNRI DISCLAIM ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL STICHTING MATHEMATISCH +CENTRUM OR CNRI BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL +DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR +PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER +TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +PERFORMANCE OF THIS SOFTWARE. + +******************************************************************/ + +#include "Python.h" +#include "macglue.h" +#include "macdefs.h" + +extern PyObject *ResObj_New Py_PROTO((Handle)); +extern int ResObj_Convert Py_PROTO((PyObject *, Handle *)); + +#include + +static PyObject *ErrorObject; + +#define PARANOID(arg) \ + if ( arg == 0 ) {PyErr_SetString(ErrorObject, "Internal error: NULL arg!"); return 0; } + +/* Prototype we use for routines */ + +typedef long anything; +typedef anything (*anyroutine) Py_PROTO((...)); + +#define MAXNAME 31 /* Maximum size of names, for printing only */ +#define MAXARG 8 /* Maximum number of arguments */ + +/* +** Routines to convert arguments between Python and C +*/ +typedef anything (*py2c_converter) Py_PROTO((PyObject *)); +typedef PyObject *(*c2py_converter) Py_PROTO((anything)); + +/* Dummy routine for arguments that are output-only */ +static anything +py2c_dummy(arg) + PyObject *arg; +{ + return 0; +} + +/* Routine to allocate storage for output integers */ +static anything +py2c_alloc(arg) + PyObject *arg; +{ + char *ptr; + + if( (ptr=malloc(sizeof(anything))) == 0 ) + PyErr_NoMemory(); + return (anything)ptr; +} + +/* Dummy routine for arguments that are input-only */ +static PyObject * +c2py_dummy(arg) + anything arg; +{ + return 0; +} + +/* Routine to de-allocate storage for input-only arguments */ +static PyObject * +c2py_free(arg) + anything arg; +{ + if ( arg ) + free((char *)arg); + return 0; +} + +/* +** None +*/ +static PyObject * +c2py_none(arg) + anything arg; +{ + if ( arg ) + free((char *)arg); + Py_INCREF(Py_None); + return Py_None; +} + +/* +** OSErr +*/ +static PyObject * +c2py_oserr(arg) + anything arg; +{ + OSErr *ptr = (OSErr *)arg; + + PARANOID(arg); + if (*ptr) { + PyErr_Mac(PyMac_OSErrException, *ptr); + free(ptr); + return NULL; + } + Py_INCREF(Py_None); + return Py_None; +} + +/* +** integers of all sizes (PPC only) +*/ +static anything +py2c_in_int(arg) + PyObject *arg; +{ + return PyInt_AsLong(arg); +} + +static PyObject * +c2py_out_long(arg) + anything arg; +{ + PyObject *rv; + + PARANOID(arg); + rv = PyInt_FromLong(*(long *)arg); + free((char *)arg); + return rv; +} + +static PyObject * +c2py_out_short(arg) + anything arg; +{ + PyObject *rv; + + PARANOID(arg); + rv = PyInt_FromLong((long)*(short *)arg); + free((char *)arg); + return rv; +} + +static PyObject * +c2py_out_byte(arg) + anything arg; +{ + PyObject *rv; + + PARANOID(arg); + rv = PyInt_FromLong((long)*(char *)arg); + free((char *)arg); + return rv; +} + +/* +** Strings +*/ +static anything +py2c_in_string(arg) + PyObject *arg; +{ + return (anything)PyString_AsString(arg); +} + +/* +** Pascal-style strings +*/ +static anything +py2c_in_pstring(arg) + PyObject *arg; +{ + unsigned char *p; + int size; + + if( (size = PyString_Size(arg)) < 0) + return 0; + if ( size > 255 ) { + PyErr_SetString(ErrorObject, "Pstring must be <= 255 chars"); + return 0; + } + if( (p=(unsigned char *)malloc(256)) == 0 ) { + PyErr_NoMemory(); + return 0; + } + p[0] = size; + memcpy(p+1, PyString_AsString(arg), size); + return (anything)p; +} + +static anything +py2c_out_pstring(arg) + PyObject *arg; +{ + unsigned char *p; + + if( (p=(unsigned char *)malloc(256)) == 0 ) { + PyErr_NoMemory(); + return 0; + } + p[0] = 0; + return (anything)p; +} + +static PyObject * +c2py_out_pstring(arg) + anything arg; +{ + unsigned char *p = (unsigned char *)arg; + PyObject *rv; + + PARANOID(arg); + rv = PyString_FromStringAndSize((char *)p+1, p[0]); + free(p); + return rv; +} + +/* +** C objects. +*/ +static anything +py2c_in_cobject(arg) + PyObject *arg; +{ + if ( arg == Py_None ) + return 0; + return (anything)PyCObject_AsVoidPtr(arg); +} + +static PyObject * +c2py_out_cobject(arg) + anything arg; +{ + void **ptr = (void **)arg; + PyObject *rv; + + PARANOID(arg); + if ( *ptr == 0 ) { + Py_INCREF(Py_None); + rv = Py_None; + } else { + rv = PyCObject_FromVoidPtr(*ptr, 0); + } + free((char *)ptr); + return rv; +} + +/* +** Handles. +*/ +static anything +py2c_in_handle(arg) + PyObject *arg; +{ + Handle h = 0; + ResObj_Convert(arg, &h); + return (anything)h; +} + +static PyObject * +c2py_out_handle(arg) + anything arg; +{ + Handle *rv = (Handle *)arg; + PyObject *prv; + + PARANOID(arg); + if ( *rv == 0 ) { + Py_INCREF(Py_None); + prv = Py_None; + } else { + prv = ResObj_New(*rv); + } + free((char *)rv); + return prv; +} + +typedef struct { + char *name; /* Name */ + py2c_converter get; /* Get argument */ + int get_uses_arg; /* True if the above consumes an argument */ + c2py_converter put; /* Put result value */ + int put_gives_result; /* True if above produces a result */ +} conventry; + +static conventry converters[] = { + {"OutNone", py2c_alloc, 0, c2py_none, 1}, + {"OutOSErr", py2c_alloc, 0, c2py_oserr, 1}, +#define OSERRORCONVERTER (&converters[1]) + {"InInt", py2c_in_int, 1, c2py_dummy, 0}, + {"OutLong", py2c_alloc, 0, c2py_out_long, 1}, + {"OutShort", py2c_alloc, 0, c2py_out_short, 1}, + {"OutByte", py2c_alloc, 0, c2py_out_byte, 1}, + {"InString", py2c_in_string, 1, c2py_dummy, 0}, + {"InPstring", py2c_in_pstring,1, c2py_free, 0}, + {"OutPstring", py2c_out_pstring,0, c2py_out_pstring,1}, + {"InCobject", py2c_in_cobject,1, c2py_dummy, 0}, + {"OutCobject", py2c_alloc, 0, c2py_out_cobject,0}, + {"InHandle", py2c_in_handle, 1, c2py_dummy, 0}, + {"OutHandle", py2c_alloc, 0, c2py_out_handle,1}, + {0, 0, 0, 0, 0} +}; + +static conventry * +getconverter(name) + char *name; +{ + int i; + char buf[256]; + + for(i=0; converters[i].name; i++ ) + if ( strcmp(name, converters[i].name) == 0 ) + return &converters[i]; + sprintf(buf, "Unknown argtype: %s", name); + PyErr_SetString(ErrorObject, buf); + return 0; +} + +static int +argparse_conv(obj, ptr) + PyObject *obj; + conventry **ptr; +{ + char *name; + int i; + conventry *item; + + if( (name=PyString_AsString(obj)) == NULL ) + return 0; + if( (item=getconverter(name)) == NULL ) + return 0; + *ptr = item; + return 1; +} + +/* ----------------------------------------------------- */ + +/* Declarations for objects of type fragment */ + +typedef struct { + PyObject_HEAD + CFragConnectionID conn_id; + char name[MAXNAME+1]; +} cdfobject; + +staticforward PyTypeObject Cdftype; + + + +/* ---------------------------------------------------------------- */ + +/* Declarations for objects of type routine */ + +typedef struct { + PyObject_HEAD + anyroutine rtn; + char name[MAXNAME+1]; +} cdrobject; + +staticforward PyTypeObject Cdrtype; + + + +/* ---------------------------------------------------------------- */ + +/* Declarations for objects of type callable */ + +typedef struct { + PyObject_HEAD + cdrobject *routine; /* The routine to call */ + int npargs; /* Python argument count */ + int npreturn; /* Python return value count */ + int ncargs; /* C argument count + 1 */ + conventry *argconv[MAXARG+1]; /* Value converter list */ +} cdcobject; + +staticforward PyTypeObject Cdctype; + + + +/* -------------------------------------------------------- */ + + +static struct PyMethodDef cdr_methods[] = { + + {NULL, NULL} /* sentinel */ +}; + +/* ---------- */ + + +static cdrobject * +newcdrobject(name, routine) + unsigned char *name; + anyroutine routine; +{ + cdrobject *self; + int nlen; + + self = PyObject_NEW(cdrobject, &Cdrtype); + if (self == NULL) + return NULL; + if ( name[0] > MAXNAME ) + nlen = MAXNAME; + else + nlen = name[0]; + memcpy(self->name, name+1, nlen); + self->name[nlen] = '\0'; + self->rtn = routine; + return self; +} + +static void +cdr_dealloc(self) + cdrobject *self; +{ + PyMem_DEL(self); +} + +static PyObject * +cdr_repr(self) + cdrobject *self; +{ + PyObject *s; + char buf[256]; + + sprintf(buf, "", self->name, self->rtn); + s = PyString_FromString(buf); + return s; +} + +static char Cdrtype__doc__[] = +"C Routine address" +; + +static PyTypeObject Cdrtype = { + PyObject_HEAD_INIT(&PyType_Type) + 0, /*ob_size*/ + "routine", /*tp_name*/ + sizeof(cdrobject), /*tp_basicsize*/ + 0, /*tp_itemsize*/ + /* methods */ + (destructor)cdr_dealloc, /*tp_dealloc*/ + (printfunc)0, /*tp_print*/ + (getattrfunc)0, /*tp_getattr*/ + (setattrfunc)0, /*tp_setattr*/ + (cmpfunc)0, /*tp_compare*/ + (reprfunc)cdr_repr, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + (hashfunc)0, /*tp_hash*/ + (ternaryfunc)0, /*tp_call*/ + (reprfunc)0, /*tp_str*/ + + /* Space for future expansion */ + 0L,0L,0L,0L, + Cdrtype__doc__ /* Documentation string */ +}; + +/* End of code for routine objects */ +/* -------------------------------------------------------- */ + + +static struct PyMethodDef cdc_methods[] = { + + {NULL, NULL} /* sentinel */ +}; + +/* ---------- */ + + +static cdcobject * +newcdcobject(routine, npargs, npreturn, ncargs, argconv) + cdrobject *routine; + int npargs; + int npreturn; + int ncargs; + conventry *argconv[]; +{ + cdcobject *self; + int i; + + self = PyObject_NEW(cdcobject, &Cdctype); + if (self == NULL) + return NULL; + self->routine = routine; + Py_INCREF(routine); + self->npargs = npargs; + self->npreturn = npreturn; + self->ncargs = ncargs; + for(i=0; iargconv[i] = argconv[i]; + else + self->argconv[i] = 0; + return self; +} + +static void +cdc_dealloc(self) + cdcobject *self; +{ + Py_XDECREF(self->routine); + PyMem_DEL(self); +} + + +static PyObject * +cdc_repr(self) + cdcobject *self; +{ + PyObject *s; + char buf[256]; + int i; + + sprintf(buf, "argconv[0]->name, self->routine->name); + for(i=1; i< self->ncargs; i++) { + strcat(buf, self->argconv[i]->name); + if ( i < self->ncargs-1 ) + strcat(buf, ", "); + } + strcat(buf, ") >"); + + s = PyString_FromString(buf); + return s; +} + +/* +** And this is what we all do it for: call a C function. +*/ +static PyObject * +cdc_call(self, args, kwargs) + cdcobject *self; + PyObject *args; + PyObject *kwargs; +{ + char buf[256]; + int i, pargindex; + anything c_args[MAXARG+1] = {0, 0, 0, 0, 0, 0, 0, 0, 0}; + conventry *cp; + PyObject *curarg; + anyroutine func; + PyObject *rv0, *rv; + + if( kwargs ) { + PyErr_SetString(PyExc_TypeError, "Keyword args not allowed"); + return 0; + } + if( !PyTuple_Check(args) ) { + PyErr_SetString(PyExc_TypeError, "Arguments not in tuple"); + return 0; + } + if( PyTuple_Size(args) != self->npargs ) { + sprintf(buf, "%d arguments, expected %d", PyTuple_Size(args), self->npargs); + PyErr_SetString(PyExc_TypeError, buf); + return 0; + } + + /* Decode arguments */ + pargindex = 0; + for(i=0; incargs; i++) { + cp = self->argconv[i]; + if ( cp->get_uses_arg ) { + curarg = PyTuple_GET_ITEM(args, pargindex); + pargindex++; + } else { + curarg = (PyObject *)NULL; + } + c_args[i] = (*cp->get)(curarg); + } + if (PyErr_Occurred()) + return 0; + + /* Call function */ + func = self->routine->rtn; + *(anything *)c_args[0] = (*func)(c_args[1], c_args[2], c_args[3], c_args[4], + c_args[5], c_args[6], c_args[7], c_args[8]); + + /* Build return tuple (always a tuple, for now */ + if( (rv=PyTuple_New(self->npreturn)) == NULL ) + return NULL; + pargindex = 0; + for(i=0; incargs; i++) { + cp = self->argconv[i]; + curarg = (*cp->put)(c_args[i]); + if( cp->put_gives_result ) + PyTuple_SET_ITEM(rv, pargindex, curarg); + /* NOTE: We only check errors at the end (so we free() everything) */ + } + if ( PyErr_Occurred() ) { + Py_DECREF(rv); + return NULL; + } + return rv; +} + +static char Cdctype__doc__[] = +"" +; + +static PyTypeObject Cdctype = { + PyObject_HEAD_INIT(&PyType_Type) + 0, /*ob_size*/ + "callable", /*tp_name*/ + sizeof(cdcobject), /*tp_basicsize*/ + 0, /*tp_itemsize*/ + /* methods */ + (destructor)cdc_dealloc, /*tp_dealloc*/ + (printfunc)0, /*tp_print*/ + (getattrfunc)0, /*tp_getattr*/ + (setattrfunc)0, /*tp_setattr*/ + (cmpfunc)0, /*tp_compare*/ + (reprfunc)cdc_repr, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + (hashfunc)0, /*tp_hash*/ + (ternaryfunc)cdc_call, /*tp_call*/ + (reprfunc)0, /*tp_str*/ + + /* Space for future expansion */ + 0L,0L,0L,0L, + Cdctype__doc__ /* Documentation string */ +}; + +/* End of code for callable objects */ +/* ---------------------------------------------------------------- */ + +static struct PyMethodDef cdf_methods[] = { + + {NULL, NULL} /* sentinel */ +}; + +/* ---------- */ + + +static cdfobject * +newcdfobject(conn_id, name) + CFragConnectionID conn_id; + unsigned char *name; +{ + cdfobject *self; + int nlen; + + self = PyObject_NEW(cdfobject, &Cdftype); + if (self == NULL) + return NULL; + self->conn_id = conn_id; + if ( name[0] > MAXNAME ) + nlen = MAXNAME; + else + nlen = name[0]; + strncpy(self->name, (char *)name+1, nlen); + self->name[nlen] = '\0'; + return self; +} + +static void +cdf_dealloc(self) + cdfobject *self; +{ + PyMem_DEL(self); +} + +static PyObject * +cdf_repr(self) + cdfobject *self; +{ + PyObject *s; + char buf[256]; + + sprintf(buf, "", self->name, self->conn_id); + s = PyString_FromString(buf); + return s; +} + +static PyObject * +cdf_getattr(self, name) + cdfobject *self; + char *name; +{ + unsigned char *rtn_name; + anyroutine rtn; + OSErr err; + Str255 errMessage; + CFragSymbolClass class; + char buf[256]; + + rtn_name = Pstring(name); + err = FindSymbol(self->conn_id, rtn_name, (Ptr *)&rtn, &class); + if ( err ) { + sprintf(buf, "%.*s: %s", rtn_name[0], rtn_name+1, PyMac_StrError(err)); + PyErr_SetString(ErrorObject, buf); + return NULL; + } + if( class != kTVectorCFragSymbol ) { + PyErr_SetString(ErrorObject, "Symbol is not a routine"); + return NULL; + } + + return (PyObject *)newcdrobject(rtn_name, rtn); +} +/* -------------------------------------------------------- */ + +static char Cdftype__doc__[] = +"Code Fragment library symbol table" +; + +static PyTypeObject Cdftype = { + PyObject_HEAD_INIT(&PyType_Type) + 0, /*ob_size*/ + "fragment", /*tp_name*/ + sizeof(cdfobject), /*tp_basicsize*/ + 0, /*tp_itemsize*/ + /* methods */ + (destructor)cdf_dealloc, /*tp_dealloc*/ + (printfunc)0, /*tp_print*/ + (getattrfunc)cdf_getattr, /*tp_getattr*/ + (setattrfunc)0, /*tp_setattr*/ + (cmpfunc)0, /*tp_compare*/ + (reprfunc)cdf_repr, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + (hashfunc)0, /*tp_hash*/ + (ternaryfunc)0, /*tp_call*/ + (reprfunc)0, /*tp_str*/ + + /* Space for future expansion */ + 0L,0L,0L,0L, + Cdftype__doc__ /* Documentation string */ +}; + +/* End of code for fragment objects */ +/* -------------------------------------------------------- */ + + +static char cdll_getlibrary__doc__[] = +"Load a shared library fragment and return the symbol table" +; + +static PyObject * +cdll_getlibrary(self, args) + PyObject *self; /* Not used */ + PyObject *args; +{ + Str255 frag_name; + OSErr err; + Str255 errMessage; + Ptr main_addr; + CFragConnectionID conn_id; + char buf[256]; + + if (!PyArg_ParseTuple(args, "O&", PyMac_GetStr255, frag_name)) + return NULL; + + /* Find the library connection ID */ + err = GetSharedLibrary(frag_name, kCurrentCFragArch, kLoadCFrag, &conn_id, &main_addr, + errMessage); + if ( err ) { + sprintf(buf, "%.*s: %s", errMessage[0], errMessage+1, PyMac_StrError(err)); + PyErr_SetString(ErrorObject, buf); + return NULL; + } + return (PyObject *)newcdfobject(conn_id, frag_name); +} + +static char cdll_getdiskfragment__doc__[] = +"Load a fragment from a disk file and return the symbol table" +; + +static PyObject * +cdll_getdiskfragment(self, args) + PyObject *self; /* Not used */ + PyObject *args; +{ + FSSpec fsspec; + Str255 frag_name; + OSErr err; + Str255 errMessage; + Ptr main_addr; + CFragConnectionID conn_id; + char buf[256]; + Boolean isfolder, didsomething; + + if (!PyArg_ParseTuple(args, "O&O&", PyMac_GetFSSpec, &fsspec, + PyMac_GetStr255, frag_name)) + return NULL; + err = ResolveAliasFile(&fsspec, 1, &isfolder, &didsomething); + if ( err ) + return PyErr_Mac(ErrorObject, err); + + /* Load the fragment (or return the connID if it is already loaded */ + err = GetDiskFragment(&fsspec, 0, 0, frag_name, + kLoadCFrag, &conn_id, &main_addr, + errMessage); + if ( err ) { + sprintf(buf, "%.*s: %s", errMessage[0], errMessage+1, PyMac_StrError(err)); + PyErr_SetString(ErrorObject, buf); + return NULL; + } + return (PyObject *)newcdfobject(conn_id, frag_name); +} + +static char cdll_newcall__doc__[] = +"" +; + +static PyObject * +cdll_newcall(self, args) + PyObject *self; /* Not used */ + PyObject *args; +{ + cdrobject *routine; + conventry *argconv[MAXARG+1] = {0, 0, 0, 0, 0, 0, 0, 0, 0}; + int npargs, npreturn, ncargs; + + /* Note: the next format depends on MAXARG+1 */ + if (!PyArg_ParseTuple(args, "O!O&|O&O&O&O&O&O&O&O&", &Cdrtype, &routine, + argparse_conv, &argconv[0], argparse_conv, &argconv[1], + argparse_conv, &argconv[2], argparse_conv, &argconv[3], + argparse_conv, &argconv[4], argparse_conv, &argconv[5], + argparse_conv, &argconv[6], argparse_conv, &argconv[7], + argparse_conv, &argconv[8])) + return NULL; + npargs = npreturn = 0; + for(ncargs=0; ncargs < MAXARG+1 && argconv[ncargs]; ncargs++) { + if( argconv[ncargs]->get_uses_arg ) npargs++; + if( argconv[ncargs]->put_gives_result ) npreturn++; + } + return (PyObject *)newcdcobject(routine, npargs, npreturn, ncargs, argconv); +} + +/* List of methods defined in the module */ + +static struct PyMethodDef cdll_methods[] = { + {"getlibrary", (PyCFunction)cdll_getlibrary, METH_VARARGS, + cdll_getlibrary__doc__}, + {"getdiskfragment", (PyCFunction)cdll_getdiskfragment, METH_VARARGS, + cdll_getdiskfragment__doc__}, + {"newcall", (PyCFunction)cdll_newcall, METH_VARARGS, + cdll_newcall__doc__}, + + {NULL, (PyCFunction)NULL, 0, NULL} /* sentinel */ +}; + + +/* Initialization function for the module (*must* be called initcalldll) */ + +static char calldll_module_documentation[] = +"" +; + +void +initcalldll() +{ + PyObject *m, *d; + + /* Create the module and add the functions */ + m = Py_InitModule4("calldll", cdll_methods, + calldll_module_documentation, + (PyObject*)NULL,PYTHON_API_VERSION); + + /* Add some symbolic constants to the module */ + d = PyModule_GetDict(m); + ErrorObject = PyString_FromString("calldll.error"); + PyDict_SetItemString(d, "error", ErrorObject); + + /* XXXX Add constants here */ + + /* Check for errors */ + if (PyErr_Occurred()) + Py_FatalError("can't initialize module calldll"); +} + +/* Test routine */ +int calldlltester(int a1,int a2,int a3,int a4,int a5,int a6,int a7,int a8) +{ + printf("Tester1: %x %x %x %x %x %x %x %x\n", a1, a2, a3, a4, a5, a6, a7, a8); + return a1; +} + -- cgit v0.12