/* Copyright (c) 2003, Steve Dekorte * All rights reserved. See _BSDLicense.txt. */ #include "IoObjcBridge.h" #include "List.h" #include "IoState.h" #include "IoList.h" #include "IoMap.h" #include "IoVector.h" #include "ObjcSubclass.h" #include "IoVector.h" #include "IoBox.h" #include "IoNumber.h" #include "Io2Objc.h" #include "Objc2Io.h" #define DATA(self) ((IoObjcBridgeData *)IoObject_dataPointer(self)) static IoObjcBridge *sharedBridge = NULL; IoObjcBridge *IoObjcBridge_sharedBridge(void) { return sharedBridge; } /*unsigned char IoObjcBridge_respondsTo(IoObject *self, IoObject *slotName) { return 1; }*/ List *IoObjcBridge_allClasses(IoObjcBridge *self) { int n; if (DATA(self)->allClasses) { return DATA(self)->allClasses; } else { Class *classes = NULL; int numClasses = 0, newNumClasses = objc_getClassList(NULL, 0); DATA(self)->allClasses = List_new(); while (numClasses < newNumClasses) { numClasses = newNumClasses; classes = objc_realloc(classes, sizeof(Class) * numClasses); newNumClasses = objc_getClassList(classes, numClasses); } for (n = 0; n < numClasses; n ++) { List_append_(DATA(self)->allClasses, classes[n]); } objc_free(classes); // memory leak - test return DATA(self)->allClasses; } } IoTag *IoObjcBridge_tag(void *state) { IoTag *tag = IoTag_newWithName_("ObjcBridge"); tag->state = state; tag->cloneFunc = (TagCloneFunc *)IoObjcBridge_rawClone; tag->freeFunc = (TagFreeFunc *)IoObjcBridge_free; tag->markFunc = (TagMarkFunc *)IoObjcBridge_mark; /*tag->respondsToFunc = (TagRespondsToFunc *)IoObjcBridge_respondsTo;*/ return tag; } IoObjcBridge *IoObjcBridge_proto(void *state) { IoObject *self = IoObject_new(state); self->tag = IoObjcBridge_tag(state); IoObject_setDataPointer_(self, objc_calloc(1, sizeof(IoObjcBridgeData))); DATA(self)->io2objcs = Hash_new(); DATA(self)->objc2ios = Hash_new(); IoObjcBridge_setMethodBuffer_(self, "nop"); sharedBridge = self; IoState_registerProtoWithFunc_(state, self, IoObjcBridge_proto); { IoMethodTable methodTable[] = { {"classNamed", IoObjcBridge_classNamed}, {"debugOn", IoObjcBridge_debugOn}, {"debugOff", IoObjcBridge_debugOff}, {"newClassWithNameAndProto", IoObjcBridge_newClassNamed_withProto_}, {"autoLookupClassNamesOn",IoObjcBridge_autoLookupClassNamesOn}, {"autoLookupClassNamesOff",IoObjcBridge_autoLookupClassNamesOff}, // Extras //{"NSSelectorFromString", IoObjcBridge_NSSelectorFromString}, //{"NSStringFromSelector", IoObjcBridge_NSStringFromSelector}, {"main", IoObjcBridge_main}, {NULL, NULL}, }; IoObject_addMethodTable_(self, methodTable); } return self; } IoObjcBridge *IoObjcBridge_rawClone(IoObjcBridge *self) { return self; } IoObjcBridge *IoObjcBridge_new(void *state) { return IoState_protoWithInitFunction_(state, IoObjcBridge_proto); } void IoObjcBridge_free(IoObjcBridge *self) { sharedBridge = NULL; { void *k = Hash_firstKey(DATA(self)->objc2ios); while (k) { id v = Hash_at_(DATA(self)->objc2ios, k); [v autorelease]; k = Hash_nextKey(DATA(self)->objc2ios); } } Hash_do_(DATA(self)->io2objcs, (HashDoCallback *)Io2Objc_nullObjcBridge); Hash_free(DATA(self)->io2objcs); Hash_free(DATA(self)->objc2ios); objc_free(DATA(self)->methodNameBuffer); objc_free(IoObject_dataPointer(self)); } void IoObjcBridge_mark(IoObjcBridge *self) { IoObject *k; /* --- mark Io2Objc objects --- */ /*k = Hash_firstKey(DATA(self)->io2objcs); while (k) { IoObject *v = Hash_at_(DATA(self)->io2objcs, k); IoObject_shouldMark(v); k = Hash_nextKey(DATA(self)->io2objcs); }*/ /* --- mark io values referenced by Objc2Io objects --- */ k = Hash_firstKey(DATA(self)->objc2ios); while (k) { id v = Hash_at_(DATA(self)->objc2ios, k); [v mark]; k = Hash_nextKey(DATA(self)->objc2ios); } [ObjcSubclass mark]; // mark io protos for ObjcSubclasses } /* ----------------------------------------------------------------- */ BOOL IoObjcBridge_rawDebugOn(IoObjcBridge *self) { return DATA(self)->debug; } IoObject *IoObjcBridge_autoLookupClassNamesOn(IoObjcBridge *self, IoObject *locals, IoMessage *m) { IoState_doCString_(IOSTATE, "Lobby forward := method(m := call message name; v := ObjcBridge classNamed(m); if(v, return v, Exception raise(\"Lookup error, slot '\" .. m ..\"' not found\")))"); return self; } IoObject *IoObjcBridge_autoLookupClassNamesOff(IoObjcBridge *self, IoObject *locals, IoMessage *m) { IoState_doCString_(IOSTATE, "Lobby removeSlot(\"forward\")"); return self; } IoObject *IoObjcBridge_debugOn(IoObjcBridge *self, IoObject *locals, IoMessage *m) { DATA(self)->debug = YES; return self; } IoObject *IoObjcBridge_debugOff(IoObjcBridge *self, IoObject *locals, IoMessage *m) { DATA(self)->debug = NO; return self; } /*IoObject *IoObjcBridge_NSSelectorFromString(IoObjcBridge *self, IoObject *locals, IoMessage *m) { IoSymbol *name = IoMessage_locals_symbolArgAt_(m, locals, 0); NSString *s = [NSString stringWithCString:CSTRING(name)]; SEL sel = NSSelectorFromString(s); return IONUMBER((int)sel); } IoObject *IoObjcBridge_NSStringFromSelector(IoObjcBridge *self, IoObject *locals, IoMessage *m) { int s = IoMessage_locals_intArgAt_(m, locals, 0); NSString *string = NSStringFromSelector((SEL)s); if (!string) return IONIL(self); return IOSYMBOL([string cString]); }*/ IoObject *IoObjcBridge_main(IoObjcBridge *self, IoObject *locals, IoMessage *m) { int argc = 1; const char *argv[] = {CSTRING(IoState_doCString_(IOSTATE, "Lobby launchPath"))}; NSApplicationMain(argc, argv); return self; } IoObject *IoObjcBridge_classNamed(IoObjcBridge *self, IoObject *locals, IoMessage *m) { IoSymbol *name = IoMessage_locals_symbolArgAt_(m, locals, 0); id obj = objc_lookUpClass(CSTRING(name)); if (!obj) { return IONIL(self); } return IoObjcBridge_proxyForId_(self, obj); } void *IoObjcBridge_proxyForId_(IoObjcBridge *self, id obj) { Io2Objc *v = Hash_at_(DATA(self)->io2objcs, obj); if (!v) { v = Io2Objc_new(IOSTATE); Io2Objc_setBridge(v, self); Io2Objc_setObject(v, obj); Hash_at_put_(DATA(self)->io2objcs, obj, v); } return v; } void *IoObjcBridge_proxyForIoObject_(IoObjcBridge *self, IoObject *v) { Objc2Io *obj = Hash_at_(DATA(self)->objc2ios, v); if (!obj) { obj = [[[Objc2Io alloc] init] autorelease]; [obj setBridge:self]; [obj setIoObject:v]; //Hash_at_put_(DATA(self)->objc2ios, IOREF(v), obj); IoObjcBridge_addValue_(self, v, obj); } return obj; } IoMessage *IoObjcBridge_ioMessageForNSInvocation_(IoObjcBridge *self, NSInvocation *invocation) { int index; BOOL debug = IoObjcBridge_rawDebugOn(self); NSMethodSignature *signature = [invocation methodSignature]; char *methodName = IoObjcBridge_ioMethodFor_(self, (char *)sel_getName([invocation selector])); IoMessage *message = IoMessage_newWithName_(IOSTATE, IoState_symbolWithCString_(IOSTATE, methodName)); const char *returnType = [[invocation methodSignature] methodReturnType]; if (!*returnType) returnType = "?"; if (debug) { IoState_print_(IOSTATE, "Objc -> Io (%s)", IoObjcBridge_nameForTypeChar_(self, *returnType)); IoState_print_(IOSTATE, "%s(", methodName); } for (index = 2; index < [signature numberOfArguments]; index++) { char *error; const char *type = [signature getArgumentTypeAtIndex:index]; unsigned char buffer[[signature argumentSizeAtIndex:index]]; if (debug) { if (2 < index) printf(", "); printf("%s", IoObjcBridge_nameForTypeChar_(self, *type)); } [invocation getArgument:buffer atIndex:index]; IoMessage_setCachedArg_to_(message, index-2, IoObjcBridge_ioValueForCValue_ofType_error_(self, buffer, (char *)type, &error)); if (error) IoState_error_(IOSTATE, message, "Io IoObjcBridge ioMessageForNSInvocation %s - argtype:'%s' argnum:%i", error, type, index-2); } if (debug) { printf(")\n"); } return message; } void IoObjcBridge_removeId_(IoObjcBridge *self, id obj) { /* Called by Io2Objc instance when freed */ Hash_removeKey_(DATA(self)->io2objcs, obj); } void IoObjcBridge_removeValue_(IoObjcBridge *self, IoObject *v) { /* Called by Obj2Io instance when dealloced */ Hash_removeKey_(DATA(self)->objc2ios, v); } void IoObjcBridge_addValue_(IoObjcBridge *self, IoObject *v, id obj) { /* Called by Obj2Io instance when alloced */ Hash_at_put_(DATA(self)->objc2ios, IOREF(v), obj); } const char *IoObjcBridge_selectorEncoding(IoObjcBridge *self, SEL selector) { struct objc_method_description *description; description = [@protocol(AddressBook) descriptionForInstanceMethod:selector]; if (description) return description->types; description = [@protocol(AppKit) descriptionForInstanceMethod:selector]; if (description) return description->types; description = [@protocol(Foundation) descriptionForInstanceMethod:selector]; if (description) return description->types; List *classes = IoObjcBridge_allClasses(self); int i, max = List_size(classes); for (i = 0; i < max; i++) { Class class = List_at_(classes, i); struct objc_method *method = class_getInstanceMethod(class, selector); if (!method) method = class_getClassMethod(class, selector); if (method) return method->method_types; } return NULL; } // ----------------------------------------------------------------- // Objective-C -> Io // ----------------------------------------------------------------- IoObject *IoObjcBridge_ioValueForCValue_ofType_error_(IoObjcBridge *self, void *cValue, char *cType, char **error) { *error = NULL; switch (*cType) { case '@': { id object = *(id *)cValue; if (!object) return IONIL(self); // else if ([object isKindOfClass:[NSString class]]) // return IOSYMBOL((char *)[object cString]); // else if ([object isKindOfClass:[NSNumber class]]) // return IONUMBER([object doubleValue]); else if ([object isKindOfClass:[Objc2Io class]]) return [object ioValue]; else return IoObjcBridge_proxyForId_(self, object); } case '#': { Class class = *(Class *)cValue; if (!class) return IONIL(self); // else if ([class isKindOfClass:[NSString class]]) // return IOSYMBOL((char *)[class cString]); // else if ([class isKindOfClass:[NSNumber class]]) // return IONUMBER([class doubleValue]); // else if ([class isKindOfClass:[Objc2Io class]]) // return [class ioValue]; else return IoObjcBridge_proxyForId_(self, class); } case ':': { SEL selector = *(SEL *)cValue; if (selector) return IOSYMBOL(sel_getName(selector)); else *error = "null selector"; break; } case 'c': return IoNumber_newWithDouble_(IOSTATE, *(char *)cValue); case 'C': return IoNumber_newWithDouble_(IOSTATE, *(unsigned char *)cValue); case 's': return IoNumber_newWithDouble_(IOSTATE, *(short *)cValue); case 'S': return IoNumber_newWithDouble_(IOSTATE, *(unsigned short *)cValue); case 'i': return IoNumber_newWithDouble_(IOSTATE, *(int *)cValue); case 'I': return IoNumber_newWithDouble_(IOSTATE, *(unsigned int *)cValue); case 'l': return IoNumber_newWithDouble_(IOSTATE, *(long *)cValue); case 'L': return IoNumber_newWithDouble_(IOSTATE, *(unsigned long *)cValue); case 'f': return IoNumber_newWithDouble_(IOSTATE, *(float *)cValue); case 'd': return IoNumber_newWithDouble_(IOSTATE, *(double *)cValue); case 'b': return IoNumber_newWithDouble_(IOSTATE, *(int *)cValue); // ? Not correct //case 'v': return IoNumber_newWithDouble_(IOSTATE, (long)cValue); // ???? //case 'r': return IoState_symbolWithCString_(IOSTATE, (char *)cValue); // Can it happen ? case '*': return IoState_symbolWithCString_(IOSTATE, *(char **)cValue); case '{': if (!strncmp(cType, "{_NSPoint=ff}", 13)) { NSPoint p = *(NSPoint *)cValue; return IoVector_newX_y_z_(IOSTATE, p.x, p.y, 0); } else if (!strncmp(cType, "{_NSSize=ff}", 12)) { NSSize s = *(NSSize *)cValue; return IoVector_newX_y_z_(IOSTATE, s.width, s.height, 0); } else if (!strncmp(cType, "{_NSRect={_NSPoint=ff}{_NSSize=ff}}", 35)) { NSRect r = *(NSRect *)cValue; return IoBox_newSet(IOSTATE, r.origin.x, r.origin.y, 0, r.size.width, r.size.height, 0); } default: *error = "no match for argument type"; } return IONIL(self); } // ----------------------------------------------------------------- // Io -> Objective-C // ----------------------------------------------------------------- void *IoObjcBridge_cValueForIoObject_ofType_error_(IoObjcBridge *self, IoObject *value, char *cType, char **error) { *error = NULL; switch (*cType) { case '@': if (ISMUTABLESEQ(value)) DATA(self)->cValue.o = [NSMutableString stringWithCString:CSTRING(value)]; else if (ISSYMBOL(value)) DATA(self)->cValue.o = [NSString stringWithCString:CSTRING(value)]; else if (ISNUMBER(value)) DATA(self)->cValue.o = [NSNumber numberWithInt:IoNumber_asInt(value)]; else if (ISIO2OBJC(value)) DATA(self)->cValue.o = Io2Objc_object(value); else if (ISNIL(value)) DATA(self)->cValue.o = nil; else if (ISLIST(value)) { char *error; int i, count = IoList_rawSize(value); id objects[count]; for (i = 0; i < count; i ++) objects[i] = *(id *)IoObjcBridge_cValueForIoObject_ofType_error_(self, IoList_rawAt_(value, i), "@", &error); DATA(self)->cValue.o = [NSArray arrayWithObjects:objects count:count]; } else if (ISMAP(value)) { char *error; IoList *list = IoMap_rawKeys(value); int i, count = IoList_rawSize(list); id keys[count], objects[count]; for (i = 0; i < count; i ++) { keys[i] = *(id *)IoObjcBridge_cValueForIoObject_ofType_error_(self, IoList_rawAt_(list, i), "@", &error); objects[i] = *(id *)IoObjcBridge_cValueForIoObject_ofType_error_(self, IoMap_rawAt(value, IoList_rawAt_(list, i)), "@", &error); } DATA(self)->cValue.o = [NSDictionary dictionaryWithObjects:objects forKeys:keys count:count]; } else DATA(self)->cValue.o = IoObjcBridge_proxyForIoObject_(self, value); break; case '#': if (ISIO2OBJC(value)) DATA(self)->cValue.class = Io2Objc_object(value); else DATA(self)->cValue.class = nil; break; case ':': if (ISSYMBOL(value)) DATA(self)->cValue.sel = sel_getUid(CSTRING(value)); else *error = "requires a string"; break; case 'c':case 'C': if (ISNUMBER(value)) DATA(self)->cValue.c = IoNumber_asInt(value); else if (ISBOOL(value)) DATA(self)->cValue.c = ISTRUE(value); else *error = "requires a number or a boolean"; break; case 's':case 'S': if (ISNUMBER(value)) DATA(self)->cValue.s = IoNumber_asInt(value); else *error = "requires a number"; break; case 'i':case 'I': if (ISNUMBER(value)) DATA(self)->cValue.i = IoNumber_asInt(value); else *error = "requires a number"; break; case 'l':case 'L': if (ISNUMBER(value)) DATA(self)->cValue.l = IoNumber_asDouble(value); else *error = "requires a number"; break; case 'f': if (ISNUMBER(value)) DATA(self)->cValue.f = IoNumber_asDouble(value); else *error = "requires a number"; break; case 'd': if (ISNUMBER(value)) DATA(self)->cValue.d = IoNumber_asDouble(value); else *error = "requires a number"; break; //case 'b': //case 'v': //case 'r': case '*': if (ISSYMBOL(value)) DATA(self)->cValue.cp = CSTRING(value); else *error = "requires a string"; break; case '^': if (!strncmp(cType, "^@", 2)) if (ISIO2OBJC(value)) DATA(self)->cValue.v = &((Io2ObjcData *)IoObject_dataPointer(value))->object; else *error = "requires an Io2Objc"; else if (!strncmp(cType, "^v", 2)) if (ISSYMBOL(value)) DATA(self)->cValue.v = CSTRING(value); else *error = "requires a string"; else *error = "no match for argument type"; break; case '{': if (!strncmp(cType, "{_NSPoint=ff}", 13)) if (ISVECTOR(value)) { IoVector *p = value; NUM_TYPE x, y, z; IoVector_rawGetXYZ(p, &x, &y, &z); DATA(self)->cValue.point.x = x; DATA(self)->cValue.point.y = y; } else *error = "requires a Point"; else if (!strncmp(cType, "{_NSSize=ff}", 12)) if (ISVECTOR(value)) { IoVector *p = value; NUM_TYPE x, y, z; IoVector_rawGetXYZ(p, &x, &y, &z); DATA(self)->cValue.size.width = x; DATA(self)->cValue.size.height = y; } else *error = "requires a Point"; else if (!strncmp(cType, "{_NSRect={_NSPoint=ff}{_NSSize=ff}}", 35)) if (ISBOX(value)) { IoVector *p1 = IoBox_rawOrigin(value); IoVector *p2 = IoBox_rawSize(value); if (p1 && p2 && ISVECTOR(p1) && ISVECTOR(p2)) { NUM_TYPE x1, y1, z1; NUM_TYPE x2, y2, z2; IoVector_rawGetXYZ(p1, &x1, &y1, &z1); IoVector_rawGetXYZ(p2, &x2, &y2, &z2); DATA(self)->cValue.rect.origin.x = x1; DATA(self)->cValue.rect.origin.y = y1; DATA(self)->cValue.rect.size.width = x2; DATA(self)->cValue.rect.size.height = y2; } else *error = "requires a Box containing 2 points"; } else *error = "requires a Box containing 2 points"; else *error = "no match for argument type"; break; default: *error = "no match for argument type"; } return &DATA(self)->cValue; } /* --- method name buffer ----------------------------------- */ void IoObjcBridge_setMethodBuffer_(IoObjcBridge *self, char *name) { int length = strlen(name); if (length > DATA(self)->methodNameBufferSize) { DATA(self)->methodNameBuffer = objc_realloc(DATA(self)->methodNameBuffer, length+1); DATA(self)->methodNameBufferSize = length; } strcpy(DATA(self)->methodNameBuffer, name); } char *IoObjcBridge_ioMethodFor_(IoObjcBridge *self, char *name) { /*IoObjcBridge_setMethodBuffer_(self, name); { char *s = DATA(self)->methodNameBuffer; while (*s) { if (*s == ':') {*s = '_';} s++; } } return DATA(self)->methodNameBuffer;*/ return name; } char *IoObjcBridge_objcMethodFor_(IoObjcBridge *self, char *name) { /*IoObjcBridge_setMethodBuffer_(self, name); { char *s = DATA(self)->methodNameBuffer; while (*s) { if (*s == '_') {*s = ':';} s++; } } return DATA(self)->methodNameBuffer;*/ return name; } /* --- new classes -------------------------------------------- */ IoObject *IoObjcBridge_newClassNamed_withProto_(IoObjcBridge *self, IoObject *locals, IoMessage *m) { IoSymbol *ioSubClassName = IoMessage_locals_symbolArgAt_(m, locals, 0); IoObject *proto = IoMessage_locals_valueArgAt_(m, locals, 1); char *subClassName = CSTRING(ioSubClassName); Class sub = objc_lookUpClass(subClassName); if (sub) IoState_error_(IOSTATE, m, "Io ObjcBridge newClassNamed_withProto_ '%s' class already exists", subClassName); sub = [ObjcSubclass newClassNamed:ioSubClassName proto:proto]; return IoObjcBridge_proxyForId_(self, sub); } char *IoObjcBridge_nameForTypeChar_(IoObjcBridge *self, char type) { switch (type) { case '@': return "id"; case '#': return "Class"; case ':': return "SEL"; case 'c': return "char"; case 'C': return "unsigned char"; case 's': return "short"; case 'S': return "unsigned short"; case 'i': return "int"; case 'I': return "unsigned int"; case 'l': return "long"; case 'L': return "unsigned long"; case 'f': return "float"; case 'd': return "double"; case 'b': return "bitfield"; case 'v': return "void"; case '?': return "undefined"; case '^': return "pointer"; case '*': return "char *"; case '[': return "array B"; case ']': return "array E"; case '(': return "union B"; case ')': return "union E"; case '{': return "struct B"; case '}': return "struct A"; } return "?"; }