]> git.saurik.com Git - cycript.git/blobdiff - ObjectiveC/Library.mm
Add String::toCYON, toString_s, and bridge NSString via String.prototype.
[cycript.git] / ObjectiveC / Library.mm
index f40d74c446280cafbaf5f1225b5d8c7f1604d22d..c1cacf86062bcea5c4123c0cf8e658f1d562914b 100644 (file)
@@ -281,6 +281,7 @@ static Class NSBoolNumber_;
 
 static Class NSArray_;
 static Class NSDictionary_;
+static Class NSString_;
 static Class Object_;
 
 static Type_privateData *Object_type;
@@ -294,6 +295,8 @@ Type_privateData *Selector_privateData::GetType() const {
     return Selector_type;
 }
 
+static JSValueRef Instance_callAsFunction_toString(JSContextRef context, JSObjectRef object, JSObjectRef _this, size_t count, const JSValueRef arguments[], JSValueRef *exception);
+
 JSValueRef CYGetClassPrototype(JSContextRef context, id self) {
     if (self == nil)
         return CYGetCachedObject(context, CYJSString("Instance_prototype"));
@@ -312,16 +315,22 @@ JSValueRef CYGetClassPrototype(JSContextRef context, id self) {
     JSClassRef _class(NULL);
     JSValueRef prototype;
 
+    JSObjectRef object(JSObjectMake(context, _class, NULL));
+
     if (self == NSArray_)
         prototype = CYGetCachedObject(context, CYJSString("Array_prototype"));
     else if (self == NSDictionary_)
         prototype = CYGetCachedObject(context, CYJSString("Object_prototype"));
-    else
+    else if (self == NSString_) {
+        prototype = CYGetCachedObject(context, CYJSString("String_prototype"));
+
+        CYSetProperty(context, object, toString_s, &Instance_callAsFunction_toString, kJSPropertyAttributeDontEnum | kJSPropertyAttributeDontDelete);
+    } else
         prototype = CYGetClassPrototype(context, class_getSuperclass(self));
 
-    JSObjectRef object(JSObjectMake(context, _class, NULL));
     JSObjectSetPrototype(context, object, prototype);
     CYSetProperty(context, cy, name, object);
+
     return object;
 }
 
@@ -403,6 +412,8 @@ JSObjectRef CYMakeInstance(JSContextRef context, id object, bool transient) {
 - (bool) cy$deleteProperty:(NSString *)name;
 - (void) cy$getPropertyNames:(JSPropertyNameAccumulatorRef)names inContext:(JSContextRef)context;
 
++ (bool) cy$hasImplicitProperties;
+
 @end
 
 @protocol Cycript
@@ -759,6 +770,10 @@ NSObject *CYCopyNSObject(apr_pool_t *pool, JSContextRef context, JSValueRef valu
     }
 }
 
++ (bool) cy$hasImplicitProperties {
+    return false;
+}
+
 @end
 /* }}} */
 /* Bridge: NSBoolNumber {{{ */
@@ -833,6 +848,10 @@ NSObject *CYCopyNSObject(apr_pool_t *pool, JSContextRef context, JSValueRef valu
     }
 }
 
++ (bool) cy$hasImplicitProperties {
+    return false;
+}
+
 @end
 /* }}} */
 /* Bridge: NSMutableArray {{{ */
@@ -994,6 +1013,10 @@ NSObject *CYCopyNSObject(apr_pool_t *pool, JSContextRef context, JSValueRef valu
 - (void) cy$getPropertyNames:(JSPropertyNameAccumulatorRef)names inContext:(JSContextRef)context {
 }
 
++ (bool) cy$hasImplicitProperties {
+    return true;
+}
+
 @end
 /* }}} */
 /* Bridge: NSProxy {{{ */
@@ -1034,6 +1057,49 @@ NSObject *CYCopyNSObject(apr_pool_t *pool, JSContextRef context, JSValueRef valu
     return [self cy$toCYON];
 }
 
+- (bool) cy$hasProperty:(NSString *)name {
+    if ([name isEqualToString:@"length"])
+        return true;
+
+    size_t index(CYGetIndex(name));
+    if (index == _not(size_t) || index >= [self length])
+        return [super cy$hasProperty:name];
+    else
+        return true;
+}
+
+- (NSObject *) cy$getProperty:(NSString *)name {
+    if ([name isEqualToString:@"length"]) {
+        NSUInteger count([self length]);
+#ifdef __APPLE__
+        return [NSNumber numberWithUnsignedInteger:count];
+#else
+        return [NSNumber numberWithUnsignedInt:count];
+#endif
+    }
+
+    size_t index(CYGetIndex(name));
+    if (index == _not(size_t) || index >= [self length])
+        return [super cy$getProperty:name];
+    else
+        return [self substringWithRange:NSMakeRange(index, 1)];
+}
+
+- (void) cy$getPropertyNames:(JSPropertyNameAccumulatorRef)names inContext:(JSContextRef)context {
+    [super cy$getPropertyNames:names inContext:context];
+
+    for (size_t index(0), length([self length]); index != length; ++index) {
+        char name[32];
+        sprintf(name, "%zu", index);
+        JSPropertyNameAccumulatorAddName(names, CYJSString(name));
+    }
+}
+
+// XXX: this might be overly restrictive for NSString; I think I need a half-way between /injecting/ implicit properties and /accepting/ implicit properties
++ (bool) cy$hasImplicitProperties {
+    return false;
+}
+
 @end
 /* }}} */
 /* Bridge: WebUndefined {{{ */
@@ -1343,6 +1409,8 @@ static void CYObjectiveC_CallFunction(JSContextRef context, ffi_cif *cif, void (
 
 static bool CYObjectiveC_PoolFFI(apr_pool_t *pool, JSContextRef context, sig::Type *type, ffi_type *ffi, void *data, JSValueRef value) { CYSadTry {
     switch (type->primitive) {
+        // XXX: do something epic about blocks
+        case sig::block_P:
         case sig::object_P:
         case sig::typename_P:
             *reinterpret_cast<id *>(data) = CYCastNSObject(pool, context, value);
@@ -1361,6 +1429,8 @@ static bool CYObjectiveC_PoolFFI(apr_pool_t *pool, JSContextRef context, sig::Ty
 
 static JSValueRef CYObjectiveC_FromFFI(JSContextRef context, sig::Type *type, ffi_type *ffi, void *data, bool initialize, JSObjectRef owner) { CYPoolTry {
     switch (type->primitive) {
+        // XXX: do something epic about blocks
+        case sig::block_P:
         case sig::object_P:
             if (NSObject *object = *reinterpret_cast<NSObject **>(data)) {
                 JSValueRef value(CYCastJSValue(context, object));
@@ -1552,6 +1622,13 @@ static void Messages_getPropertyNames(JSContextRef context, JSObjectRef object,
 #endif
 }
 
+static bool CYHasImplicitProperties(Class _class) {
+    // XXX: this is an evil hack to deal with NSProxy; fix elsewhere
+    if (!CYImplements(_class, object_getClass(_class), @selector(cy$hasImplicitProperties), false))
+        return true;
+    return [_class cy$hasImplicitProperties];
+}
+
 static bool Instance_hasProperty(JSContextRef context, JSObjectRef object, JSStringRef property) {
     Instance *internal(reinterpret_cast<Instance *>(JSObjectGetPrivate(object)));
     id self(internal->GetValue());
@@ -1582,9 +1659,10 @@ static bool Instance_hasProperty(JSContextRef context, JSObjectRef object, JSStr
         return true;
 #endif
 
-    if (SEL sel = sel_getUid(string))
-        if (CYImplements(self, _class, sel, true))
-            return true;
+    if (CYHasImplicitProperties(_class))
+        if (SEL sel = sel_getUid(string))
+            if (CYImplements(self, _class, sel, true))
+                return true;
 
     return false;
 }
@@ -1619,9 +1697,10 @@ static JSValueRef Instance_getProperty(JSContextRef context, JSObjectRef object,
     }
 #endif
 
-    if (SEL sel = sel_getUid(string))
-        if (CYImplements(self, _class, sel, true))
-            return CYSendMessage(pool, context, self, NULL, sel, 0, NULL, false, exception);
+    if (CYHasImplicitProperties(_class))
+        if (SEL sel = sel_getUid(string))
+            if (CYImplements(self, _class, sel, true))
+                return CYSendMessage(pool, context, self, NULL, sel, 0, NULL, false, exception);
 
     return NULL;
 } CYCatch }
@@ -1695,6 +1774,18 @@ static bool Instance_deleteProperty(JSContextRef context, JSObjectRef object, JS
     } CYPoolCatch(NULL)
 } CYCatch return /*XXX*/ NULL; }
 
+static void Instance_getPropertyNames_message(JSPropertyNameAccumulatorRef names, objc_method *method) {
+    const char *name(sel_getName(method_getName(method)));
+    if (strchr(name, ':') != NULL)
+        return;
+
+    const char *type(method_getTypeEncoding(method));
+    if (type == NULL || *type == '\0' || *type == 'v')
+        return;
+
+    JSPropertyNameAccumulatorAddName(names, CYJSString(name));
+}
+
 static void Instance_getPropertyNames(JSContextRef context, JSObjectRef object, JSPropertyNameAccumulatorRef names) {
     Instance *internal(reinterpret_cast<Instance *>(JSObjectGetPrivate(object)));
     id self(internal->GetValue());
@@ -1712,6 +1803,21 @@ static void Instance_getPropertyNames(JSContextRef context, JSObjectRef object,
     }
 #endif
 
+    if (CYHasImplicitProperties(_class))
+        for (Class current(_class); current != nil; current = class_getSuperclass(current)) {
+#if OBJC_API_VERSION >= 2
+            unsigned int size;
+            objc_method **data(class_copyMethodList(current, &size));
+            for (size_t i(0); i != size; ++i)
+                Instance_getPropertyNames_message(names, data[i]);
+            free(data);
+#else
+            for (objc_method_list *methods(current->methods); methods != NULL; methods = methods->method_next)
+                for (int i(0); i != methods->method_count; ++i)
+                    Instance_getPropertyNames_message(names, &methods->method_list[i]);
+#endif
+        }
+
     CYPoolTry {
         // XXX: this is an evil hack to deal with NSProxy; fix elsewhere
         if (CYImplements(self, _class, @selector(cy$getPropertyNames:inContext:), false))
@@ -2306,6 +2412,7 @@ void CYObjectiveC_Initialize() { /*XXX*/ JSContextRef context(NULL); CYPoolTry {
 
     NSArray_ = objc_getClass("NSArray");
     NSDictionary_ = objc_getClass("NSDictionary");
+    NSString_ = objc_getClass("NSString");
     Object_ = objc_getClass("Object");
 
     JSClassDefinition definition;