Initial revision
diff --git a/Modules/objc.c b/Modules/objc.c
new file mode 100644
index 0000000..9e70912
--- /dev/null
+++ b/Modules/objc.c
@@ -0,0 +1,651 @@
+/***********************************************************
+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 not be used in advertising or publicity pertaining to
+distribution of the software without specific, written prior permission.
+
+STICHTING MATHEMATISCH CENTRUM DISCLAIMS ALL WARRANTIES WITH REGARD TO
+THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
+FITNESS, IN NO EVENT SHALL STICHTING MATHEMATISCH CENTRUM 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.
+
+******************************************************************/
+
+/* Objective-C interface for NeXTStep */
+/* Tested with NeXTStep 3.3 on Intel and Sparc architectures */
+
+/* Original author: Jon M. Kutemeier */
+/* Revamped and maintained by: Guido van Rossum */
+
+/* XXX To do:
+   - bug??? x.send('name', []) gives weird error
+   - rename functions from objc_* to ObjC_*
+   - change send(sel, [a, b, c]) to send(self, a, b, c)
+   - call back to Python from Objective-C
+ */
+
+/* Python header file */
+#include "Python.h"
+
+/* NeXT headers */
+#include <sys/param.h>
+#include <mach-o/rld.h>
+#include <objc/objc.h>
+#include <objc/objc-runtime.h>
+#import <remote/NXProxy.h>
+
+/* Distinguish between ObjC classes and instances */
+typedef enum {
+    OBJC_CLASS,
+    OBJC_INSTANCE,
+} ObjC_Typecode;
+
+/* Exception raised for ObjC specific errors */
+static PyObject *ObjC_Error;
+
+/* Python wrapper about ObjC id (instance or class) */
+typedef struct {
+    PyObject_HEAD
+    id             obj;
+    ObjC_Typecode  type;
+    int            owned;
+} ObjCObject;
+
+/* Corresponding Python type object */
+staticforward PyTypeObject ObjC_Type;
+
+/* Corresponding Python type check macro */
+#define ObjC_Check(o) ((o)->ob_type == &ObjC_Type)
+
+/* Create a new ObjCObject */
+static ObjCObject *
+newObjCObject(obj, type, owned)
+    id obj;
+    ObjC_Typecode type;
+    int owned;
+{
+    ObjCObject *self;
+
+    self = PyObject_NEW(ObjCObject, &ObjC_Type);
+    if (self == NULL)
+	return NULL;
+
+    self->obj = obj;
+    self->type = type;
+    self->owned = owned;
+
+    return self;
+}
+
+static void
+objc_sendfree(self)
+    ObjCObject *self;
+{
+    if (self->obj)
+	self->obj = (id)objc_msgSend(self->obj, SELUID("free"));
+}
+
+/* Deallocate an ObjCObject */
+static void
+objc_dealloc(self)
+    ObjCObject *self;
+{
+    if (self->owned)
+	objc_sendfree(self);
+    PyMem_DEL(self);
+}
+
+/* Return a string representation of an ObjCObject */
+static PyObject *
+objc_repr(self)
+    ObjCObject *self;
+{
+    char buffer[512];
+    char *p = buffer;
+    if (self->obj == nil)
+	p = "<Objective-C nil>";
+    else {
+	char *t;
+	switch (self->type) {
+	case OBJC_CLASS: t = "class"; break;
+	case OBJC_INSTANCE: t = "instance"; break;
+	default: t = "???"; break;
+	}
+	sprintf(buffer, "<Objective-C %s %s at %lx>",
+		NAMEOF(self->obj), t, (long)(self->obj));
+    }
+    return PyString_FromString(p);
+}
+
+/*** ObjCObject methods ***/
+
+/* Call an object's free method */
+static PyObject *
+objc_free(self, args)
+    ObjCObject *self;
+    PyObject *args;
+{
+    if (!PyArg_ParseTuple(args, ""))
+	return NULL;
+    objc_sendfree(self);
+}
+
+/* Send a message to an ObjCObject.
+   The Python call looks like e.g. obj.send('moveTo::', [arg1, arg2])
+   which translates into Objective-C as [obj moveTo: arg1 : arg2] */
+static PyObject *
+objc_send(self, args)
+    ObjCObject *self;
+    PyObject *args;
+{
+    char *methodname;
+    char *margBuff = NULL;
+    PyObject *retobject = NULL;
+    PyObject *arglist;
+    id receiver, obj;
+    char *type;
+    SEL sel;
+    Method meth;
+    unsigned int margCount, margSize;
+    int offset, i;
+
+    if (!PyArg_ParseTuple(args, "sO!", &methodname, &PyList_Type, &arglist))
+	return NULL;
+
+    /* Get the method descriptor from the object */
+
+    receiver = self->obj;
+    sel = SELUID(methodname);
+
+    switch(self->type) {
+    case OBJC_CLASS:
+	meth = class_getClassMethod(receiver->isa, sel);
+	break;
+    case OBJC_INSTANCE:
+	meth = class_getInstanceMethod(receiver->isa, sel);
+	break;
+    default:
+	PyErr_SetString(ObjC_Error,
+			"receiver's type is neither instance not class!?!?");
+	return NULL;
+    }
+
+    if (!meth) {
+	PyErr_SetString(ObjC_Error, "receiver has no method by that name");
+	return NULL;
+    }
+
+    /* Fill in the argument list, type-checking the arguments */
+
+    margCount = method_getNumberOfArguments(meth);
+
+    if (PyList_Size(arglist) + 2 != margCount) {
+	PyErr_SetString(ObjC_Error,
+			"wrong number of arguments for this method");
+	    return NULL;
+    }
+
+    margSize = method_getSizeOfArguments(meth);
+    margBuff = PyMem_NEW(char, margSize+1);
+    if (margBuff == NULL)
+	return PyErr_NoMemory();
+
+    method_getArgumentInfo(meth, 0, &type, &offset);
+    marg_setValue(margBuff, offset, id, receiver);
+
+    method_getArgumentInfo(meth, 1, &type, &offset);
+    marg_setValue(margBuff, offset, SEL, sel);
+
+    for (i = 2; i < margCount; i++) {
+	PyObject *argument;
+	method_getArgumentInfo(meth, i, &type, &offset);
+
+	argument = PyList_GetItem(arglist, i-2);
+
+	/* scan past protocol-type modifiers */
+	while (strchr("rnNoOV", *type) != 0)
+	    type++;
+
+	/* common type checks */
+	switch(*type) {
+
+	    /* XXX The errors here should point out which argument */
+
+	case 'c':
+	case '*':
+	case 'C':
+	    if (!PyString_Check(argument)) {
+		PyErr_SetString(ObjC_Error, "string argument expected");
+		goto error;
+	    }
+	    break;
+
+	case 'i':
+	case 's':
+	case 'I':
+	case 'S':
+	case 'l':
+	case 'L':
+	case '^':
+	    if (!PyInt_Check(argument)) {
+		PyErr_SetString(ObjC_Error, "integer argument expected");
+		goto error;
+	    }
+	    break;
+
+	case 'f':
+	case 'd':
+	    if (!PyFloat_Check(argument)) {
+		PyErr_SetString(ObjC_Error, "float argument expected");
+		goto error;
+	    }
+	    break;
+
+	}
+
+	/* convert and store the argument */
+	switch (*type) {
+
+	case 'c': /* char */
+	    marg_setValue(margBuff, offset, char,
+			  PyString_AsString(argument)[0]);
+	    break;
+
+	case 'C': /* unsigned char */
+	    marg_setValue(margBuff, offset, unsigned char,
+			  PyString_AsString(argument)[0]);
+	    break;
+
+	case '*': /* string */
+	    marg_setValue(margBuff, offset, char *,
+			  PyString_AsString(argument));
+	    break;
+
+	case 'i': /* int */
+	    marg_setValue(margBuff, offset, int,
+			  PyInt_AsLong(argument));
+	    break;
+
+	case 'I': /* unsigned int */
+	    marg_setValue(margBuff, offset, unsigned int,
+			  PyInt_AsLong(argument));
+	    break;
+
+	case 's': /* short */
+	    marg_setValue(margBuff, offset, short,
+			  PyInt_AsLong(argument));
+	    break;
+
+	case 'S': /* unsigned short */
+	    marg_setValue(margBuff, offset, unsigned short,
+			  PyInt_AsLong(argument));
+	    break;
+
+	case 'l': /* long */
+	    marg_setValue(margBuff, offset, long,
+			  PyInt_AsLong(argument));
+	    break;
+
+	case 'L': /* unsigned long */
+	    marg_setValue(margBuff, offset, unsigned long,
+			  PyInt_AsLong(argument));
+	    break;
+
+	case 'f': /* float */
+	    marg_setValue(margBuff, offset, float,
+			  (float)PyFloat_AsDouble(argument));
+	    break;
+
+	case 'd': /* double */
+	    marg_setValue(margBuff, offset, double,
+			  PyFloat_AsDouble(argument));
+	    break;
+
+	case '@': /* id (or None) */
+	    if (ObjC_Check(argument))
+		marg_setValue(margBuff, offset, id,
+			      ((ObjCObject *)(argument))->obj);
+	    else if (argument == Py_None)
+		marg_setValue(margBuff, offset, id, nil);
+	    else {
+		PyErr_SetString(ObjC_Error, "id or None argument expected");
+		goto error;
+	    }
+	    break;
+
+	case '^': /* void * (use int) */
+	    marg_setValue(margBuff, offset, void *,
+			  (void *)PyInt_AsLong(argument));
+	    break;
+
+	case ':': /* SEL (use string or int) */
+	    if (PyInt_Check(argument))
+		marg_setValue(margBuff, offset, SEL,
+			      (SEL)PyInt_AsLong(argument));
+	    else if (PyString_Check(argument))
+		marg_setValue(margBuff, offset, SEL,
+			      SELUID(PyString_AsString(argument)));
+	    else {
+		PyErr_SetString(ObjC_Error,
+				"selector string or int argument expected");
+		goto error;
+	    }
+	    break;
+
+	case '#': /* Class (may also use int) */
+	    if (ObjC_Check(argument) &&
+		((ObjCObject *)argument)->type == OBJC_INSTANCE)
+		marg_setValue(margBuff, offset, Class *,
+			      (Class *)((ObjCObject *)argument)->obj);
+	    else if (PyInt_Check(argument))
+		marg_setValue(margBuff, offset, Class *,
+			      (Class *)PyInt_AsLong(argument));
+	    else {
+		PyErr_SetString(ObjC_Error,
+				"ObjC class object required");
+		goto error;
+	    }
+	    break;
+
+	default:
+	    PyErr_SetString(ObjC_Error, "unknown argument type");
+	    goto error;
+
+	}
+    }
+
+    /* Call the method and set the return value */
+
+    type = meth->method_types;
+
+    while (strchr("rnNoOV", *type))
+	type++;
+
+    switch(*type) {
+
+/* Cast objc_msgSendv to a function returning the right thing */
+#define MS_CAST(type) ((type (*)())objc_msgSendv)
+
+    case 'c':
+    case '*':
+    case 'C':
+	retobject = (PyObject *)PyString_FromString(
+	    MS_CAST(char *)(receiver, sel, margSize, margBuff));
+	break;
+
+    case 'i':
+    case 's':
+    case 'I':
+    case 'S':
+	retobject = (PyObject *)PyInt_FromLong(
+	    MS_CAST(int)(receiver, sel, margSize, margBuff));
+	break;
+
+    case 'l':
+    case 'L':
+    case '^':
+	retobject = (PyObject *)PyInt_FromLong(
+	    MS_CAST(long)(receiver, sel, margSize, margBuff));
+	break;
+
+    case 'f':
+	retobject = (PyObject *)PyFloat_FromDouble(
+	    MS_CAST(float)(receiver, sel, margSize, margBuff));
+	break;
+
+    case 'd':
+	retobject = (PyObject *)PyFloat_FromDouble(
+	    MS_CAST(double)(receiver, sel, margSize, margBuff));
+	break;
+
+    case '@':
+	obj = MS_CAST(id)(receiver, sel, margSize, margBuff);
+	if (obj == nil) {
+	    retobject = Py_None;
+	    Py_INCREF(retobject);
+	}
+	else if (obj != receiver)
+	    retobject = (PyObject *)newObjCObject(obj, OBJC_INSTANCE, 0);
+	else {
+	    retobject = (PyObject *)self;
+	    Py_INCREF(retobject);
+	}
+	break;
+
+    case ':':
+	retobject = (PyObject *)PyInt_FromLong(
+	    (long)MS_CAST(SEL)(receiver, sel, margSize, margBuff));
+	break;
+
+    case '#':
+	retobject = (PyObject *)PyInt_FromLong(
+	    (long)MS_CAST(Class *)(receiver, sel, margSize, margBuff));
+	break;
+
+#undef MS_CAST
+
+    }
+
+  error:
+    PyMem_XDEL(margBuff);
+    return retobject;
+}
+
+/* List of methods for ObjCObject */
+static PyMethodDef objc_methods[] = {
+    {"send",	(PyCFunction)objc_send, 1},
+    {"free",	(PyCFunction)objc_free, 1},
+    {NULL,	NULL}		/* sentinel */
+};
+
+/* Get an attribute of an ObjCObject */
+static PyObject *
+objc_getattr(self, name)
+    ObjCObject *self;
+    char *name;
+{
+    PyObject *method;
+
+    /* Try a function method */
+    method = Py_FindMethod(objc_methods, (PyObject *)self, name);
+    if (method != NULL)
+	return method;
+    PyErr_Clear();
+
+    /* Try an instance variable */
+    if (strcmp(name, "obj") == 0)
+	return PyInt_FromLong((long)self->obj);
+    if (strcmp(name, "type") == 0)
+	return PyInt_FromLong((long)self->type);
+    if (strcmp(name, "owned") == 0)
+	return PyInt_FromLong((long)self->owned);
+    if (strcmp(name, "name") == 0)
+	return PyString_FromString(NAMEOF(self->obj));
+    if (strcmp(name, "__members__") == 0)
+	return Py_BuildValue("[sss]", "name", "obj", "owned", "type");
+
+    PyErr_SetString(PyExc_AttributeError, name);
+    return NULL;
+}
+
+/* The type object */
+static PyTypeObject ObjC_Type = {
+    PyObject_HEAD_INIT(&PyType_Type)
+    0,                                  /*ob_size*/
+    "objc",                             /*tp_name*/
+    sizeof(ObjCObject),                 /*tp_basicsize*/
+    0,                                  /*tp_itemsize*/
+    /* methods */
+    (destructor)objc_dealloc,           /*tp_dealloc*/
+    0,                         		/*tp_print*/
+    (getattrfunc)objc_getattr,          /*tp_getattr*/
+    0,                       		/*tp_setattr*/
+    0,                         		/*tp_compare*/
+    (reprfunc)objc_repr,		/*tp_repr*/
+    0,                                  /*tp_as_number*/
+    0,                                  /*tp_as_sequence*/
+    0,                                  /*tp_as_mapping*/
+    0,     		                /*tp_hash*/
+    0,					/*tp_call*/
+    0,					/*tp_str*/
+    0, 0, 0, 0,				/*xxx1-4*/
+    "Objective-C id wrapper",		/*tp_doc*/
+};
+
+
+
+/*** Top-level functions ***/
+
+/* Max #files passed to loadobjectfile() */
+#define MAXRLD 128
+
+/* Load a list of object files */
+static PyObject *
+objc_loadobjectfiles(self, args)
+PyObject *self;   /* Not used */
+PyObject *args;
+{
+    NXStream *errorStream;
+    struct mach_header *new_header;
+    const char *filenames[MAXRLD+1];
+    long ret;
+    char *streamBuf;
+    PyObject *filelist, *file;
+    int listsize, len, maxLen, i;
+
+    if (!PyArg_ParseTuple(args, "O!", &PyList_Type, &filelist))
+	return NULL;
+
+    listsize = PyList_Size(filelist);
+
+    if (listsize > MAXRLD) {
+	PyErr_SetString(ObjC_Error, "more than 128 files in list");
+	return NULL;
+    }
+
+    errorStream = NXOpenMemory(NULL, 0, NX_WRITEONLY);
+
+    for (i = 0; i < listsize; i++) {
+	file = PyList_GetItem(filelist, i);
+
+	if (!PyString_Check(file))
+	    {
+		PyErr_SetString(ObjC_Error,
+				"all list items must be strings");
+		return NULL;
+	    }
+
+	filenames[i] = PyString_AsString(file);
+    }
+
+    filenames[listsize] = NULL;
+
+    ret = objc_loadModules(filenames, errorStream, NULL, &new_header, NULL);
+
+    /* extract the error messages for the exception */
+
+    if(ret) {
+	NXPutc(errorStream, (char)0);
+
+	NXGetMemoryBuffer(errorStream, &streamBuf, &len, &maxLen);
+	PyErr_SetString(ObjC_Error, streamBuf);
+    }
+
+    NXCloseMemory(errorStream, NX_FREEBUFFER);
+
+    if(ret)
+	return NULL;
+
+    Py_XINCREF(Py_None);
+    return Py_None;
+}
+
+static PyObject *
+objc_lookupclass(self, args)
+PyObject *self;   /* Not used */
+PyObject *args;
+{
+    char *classname;
+    id    class;
+
+    if (!PyArg_ParseTuple(args, "s", &classname))
+	return NULL;
+
+    if (!(class = objc_lookUpClass(classname)))
+	{
+	    PyErr_SetString(ObjC_Error, "unknown ObjC class");
+	    return NULL;
+	}
+
+    return (PyObject *)newObjCObject(class, OBJC_CLASS, 0);
+}
+
+/* List all classes */
+static PyObject *
+objc_listclasses(self, args)
+    ObjCObject *self;
+    PyObject *args;
+{
+    NXHashTable *class_hash = objc_getClasses();
+    NXHashState state = NXInitHashState(class_hash);
+    Class classid;
+    PyObject *list;
+
+    if (!PyArg_ParseTuple(args, ""))
+	return NULL;
+
+    list = PyList_New(0);
+    if (list == NULL)
+	return NULL;
+  
+    while (NXNextHashState(class_hash, &state, (void**)&classid)) {
+	ObjCObject *item = newObjCObject(classid, OBJC_CLASS, 0);
+	if (item == NULL || PyList_Append(list, (PyObject *)item) < 0) {
+	    Py_XDECREF(item);
+	    Py_DECREF(list);
+	    return NULL;
+	}
+	Py_INCREF(item);
+    }
+
+    return list;
+}
+
+/* List of top-level functions */
+static PyMethodDef objc_class_methods[] = {
+    {"loadobjectfiles",	objc_loadobjectfiles, 1},
+    {"lookupclass", 	objc_lookupclass, 1},
+    {"listclasses",	objc_listclasses, 1},
+    {NULL, 		NULL}		/* sentinel */
+};
+
+/* Initialize for the module */
+void
+initobjc()
+{
+    PyObject *m, *d;
+
+    m = Py_InitModule("objc", objc_class_methods);
+    d = PyModule_GetDict(m);
+
+    ObjC_Error = PyString_FromString("objc.error");
+    PyDict_SetItemString(d, "error", ObjC_Error);
+
+    if (PyErr_Occurred())
+	Py_FatalError("can't initialize module objc");
+
+#ifdef WITH_THREAD
+    objc_setMultithreaded(1);
+#endif
+}