Created
November 13, 2011 18:56
-
-
Save hackfrag/1362488 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// JSContext.h | |
// CommanderCool | |
// | |
// Created by Florian Strauß on 13.11.11. | |
// Copyright (c) 2011 Corporation. All rights reserved. | |
// | |
#import <JavaScriptCore/JavaScriptCore.h> | |
#import <objc/runtime.h> | |
@protocol JSObject <NSObject> | |
@required | |
- (JSStaticFunction*)staticsMethodes; | |
- (SEL)selectorForJavascriptMethod:(NSString *)methodName; | |
- (NSString *)name; | |
@end | |
@interface JSContext : NSObject | |
+ (JSContext *)sharedContext; | |
- (void)addObject:(id<JSObject>)object; | |
- (NSString *)evaluate:(NSString *)javascriptString; | |
- (JSObjectCallAsFunctionCallback)callback; | |
@end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// JSContext.m | |
// CommanderCool | |
// | |
// Created by Florian Strauß on 13.11.11. | |
// Copyright (c) 2011 Corporation. All rights reserved. | |
// | |
#import "JSContext.h" | |
NSString* JSValueToNSString(JSContextRef context, JSValueRef value) { | |
JSStringRef string = JSValueToStringCopy(context, value, NULL); | |
return (NSString*)JSStringCopyCFString(kCFAllocatorDefault, string); | |
} | |
NSString* JSObjectToNSString(JSContextRef context, JSObjectRef object) { | |
JSValueRef value = (JSValueRef)object; | |
return JSValueToNSString(context, value); | |
} | |
JSValueRef getPropertyCallback(JSContextRef ctx, JSObjectRef object, JSStringRef propertyName, JSValueRef* exception) { | |
id<JSObject> instance = (id<JSObject>)JSObjectGetPrivate(object); | |
NSString *propertyString = (NSString *)JSStringCopyCFString(kCFAllocatorDefault, propertyName); | |
SEL selector = NSSelectorFromString(propertyString); | |
if([instance respondsToSelector:selector]) { | |
// maybe selector is a method not a property getter | |
objc_property_t theProperty = class_getProperty([instance class], [propertyString UTF8String]); | |
if(theProperty == NULL) { | |
return NO; | |
} | |
// property Type | |
const char *type = property_getAttributes(theProperty); | |
NSString *typeString = [NSString stringWithUTF8String:type]; | |
NSArray *attributes = [typeString componentsSeparatedByString:@","]; | |
NSString *typeAttribute = [attributes objectAtIndex:0]; | |
NSString *propertyType = [typeAttribute substringFromIndex:1]; | |
const char *rawPropertyType = [propertyType UTF8String]; | |
// prepare invocation of the getter selector | |
NSMethodSignature *mySignature = [[instance class] instanceMethodSignatureForSelector:selector]; | |
NSInvocation *invokation = [NSInvocation invocationWithMethodSignature:mySignature]; | |
[invokation setTarget:instance]; | |
[invokation setSelector:selector]; | |
[invokation invoke]; | |
if (strcmp(rawPropertyType, @encode(float)) == 0) { | |
float returnValue; | |
[invokation getReturnValue:&returnValue]; | |
return JSValueMakeNumber(ctx, returnValue); | |
} else if (strcmp(rawPropertyType, @encode(double)) == 0) { | |
double returnValue; | |
[invokation getReturnValue:&returnValue]; | |
return JSValueMakeNumber(ctx, returnValue); | |
} else if (strcmp(rawPropertyType, @encode(int)) == 0) { | |
int returnValue; | |
[invokation getReturnValue:&returnValue]; | |
return JSValueMakeNumber(ctx, returnValue); | |
} else if (strcmp(rawPropertyType, @encode(BOOL)) == 0) { | |
BOOL returnValue; | |
[invokation getReturnValue:&returnValue]; | |
return JSValueMakeBoolean(ctx, returnValue); | |
} else if (strcmp(rawPropertyType, "@\"NSString\"") == 0) { | |
NSString *returnValue; | |
[invokation getReturnValue:&returnValue]; | |
return JSValueMakeString(ctx, JSStringCreateWithUTF8CString([returnValue cStringUsingEncoding:NSUTF8StringEncoding])); | |
} else { | |
return NULL; | |
} | |
} | |
return NULL; | |
} | |
bool setPropertyCallback(JSContextRef ctx, JSObjectRef object, JSStringRef propertyName, JSValueRef value, JSValueRef* exception) { | |
id<JSObject> instance = (id<JSObject>) JSObjectGetPrivate(object); | |
// Getting setter Selector | |
NSString *propertyString = (NSString *)JSStringCopyCFString(kCFAllocatorDefault, propertyName); | |
NSString *propertyStringUppercase = [propertyString stringByReplacingCharactersInRange:NSMakeRange(0,1) | |
withString:[[propertyString substringToIndex:1] uppercaseString]]; | |
NSString *setSelectorString = [NSString stringWithFormat:@"set%@:",propertyStringUppercase]; | |
SEL selector = NSSelectorFromString(setSelectorString); | |
if([instance respondsToSelector:selector]) { | |
objc_property_t theProperty = class_getProperty([instance class], [propertyString UTF8String]); | |
const char *type = property_getAttributes(theProperty); | |
NSString *typeString = [NSString stringWithUTF8String:type]; | |
NSArray *attributes = [typeString componentsSeparatedByString:@","]; | |
NSString *typeAttribute = [attributes objectAtIndex:0]; | |
NSString *propertyType = [typeAttribute substringFromIndex:1]; | |
const char *rawPropertyType = [propertyType UTF8String]; | |
NSString *ivarString = [NSString stringWithFormat:@"_%@",propertyString]; | |
Ivar ivar = class_getInstanceVariable([instance class], [ivarString UTF8String]); | |
if (strcmp(rawPropertyType, @encode(float)) == 0) { | |
float *ivarPtr = (float *)((uint8_t *)instance + ivar_getOffset(ivar)); | |
*ivarPtr = (float)JSValueToNumber(ctx, value, NULL); | |
return YES; | |
} else if (strcmp(rawPropertyType, @encode(double)) == 0) { | |
double *ivarPtr = (double*)((uint8_t *)instance + ivar_getOffset(ivar)); | |
*ivarPtr = JSValueToNumber(ctx, value, NULL); | |
return YES; | |
} else if (strcmp(rawPropertyType, @encode(int)) == 0) { | |
int *ivarPtr = (int *)((uint8_t *)instance + ivar_getOffset(ivar)); | |
*ivarPtr = (int)JSValueToNumber(ctx, value, NULL); | |
return YES; | |
} else if (strcmp(rawPropertyType, @encode(BOOL)) == 0) { | |
BOOL *ivarPtr = (BOOL *)((uint8_t *)instance + ivar_getOffset(ivar)); | |
*ivarPtr = JSValueToBoolean(ctx, value); | |
return YES; | |
} else if (strcmp(rawPropertyType, "@\"NSString\"") == 0) { | |
[instance setValue:JSValueToNSString(ctx, value) forKey:propertyString]; | |
return YES; | |
} else { | |
return NO; | |
} | |
} | |
return NO; | |
} | |
JSValueRef functionCallback(JSContextRef context, | |
JSObjectRef function, | |
JSObjectRef thisObject, | |
size_t argumentCount, | |
const JSValueRef arguments[], | |
JSValueRef *exception) | |
{ | |
// Parsing Method Name | |
NSString *functionString = JSObjectToNSString(context, function); | |
NSArray *functionValues = [functionString componentsSeparatedByString:@" "]; | |
NSString *functionName = [functionValues objectAtIndex:1]; | |
NSString *functionSingle = [functionName stringByReplacingCharactersInRange:NSMakeRange([functionName length] - 2, 2) withString:@""]; | |
id<JSObject> object = (id<JSObject>)JSObjectGetPrivate(thisObject); | |
Class klass = [object class]; | |
SEL selector = [object selectorForJavascriptMethod:functionSingle]; | |
if([object respondsToSelector:selector]) { | |
NSMethodSignature *mySignature = [klass instanceMethodSignatureForSelector:selector]; | |
NSInvocation *myInvocation = [NSInvocation invocationWithMethodSignature:mySignature]; | |
[myInvocation setTarget:object]; | |
[myInvocation setSelector:selector]; | |
if(([mySignature numberOfArguments] - 2) >= argumentCount) { | |
for (int i = 0; i < argumentCount; i++) { | |
JSValueRef argument = arguments[i]; | |
if(JSValueIsNumber(context, argument)) { | |
Method methode = class_getInstanceMethod(klass, selector); | |
char *methodArgumentType = method_copyArgumentType(methode, i + 2); | |
if (strcmp(methodArgumentType, @encode(float)) == 0) { | |
float numberValue = (float)JSValueToNumber(context, argument, NULL); | |
[myInvocation setArgument:&numberValue atIndex:(i + 2)]; | |
} else if (strcmp(methodArgumentType, @encode(double)) == 0) { | |
double numberValue = (double)JSValueToNumber(context, argument, NULL); | |
[myInvocation setArgument:&numberValue atIndex:(i + 2)]; | |
} else if (strcmp(methodArgumentType, @encode(int)) == 0) { | |
int numberValue = (int)JSValueToNumber(context, argument, NULL); | |
[myInvocation setArgument:&numberValue atIndex:(i + 2)]; | |
} else if (strcmp(methodArgumentType, @encode(BOOL)) == 0) { | |
BOOL numberValue = JSValueToBoolean(context, argument); | |
[myInvocation setArgument:&numberValue atIndex:(i + 2)]; | |
} else { | |
[myInvocation setArgument:nil atIndex:(i + 2)]; | |
} | |
} else if(JSValueIsString(context, argument)) { | |
NSString *stringValue = JSValueToNSString(context, argument); | |
[myInvocation setArgument:&stringValue atIndex:(i + 2)]; | |
} else if(JSValueIsBoolean(context, argument)) { | |
BOOL boolValue = JSValueToBoolean(context, argument); | |
[myInvocation setArgument:&boolValue atIndex:(i + 2)]; | |
} | |
} | |
} | |
[myInvocation invoke]; | |
if (strcmp([mySignature methodReturnType], @encode(float)) == 0) { | |
float returnValue; | |
[myInvocation getReturnValue:&returnValue]; | |
return JSValueMakeNumber(context, returnValue); | |
} else if (strcmp([mySignature methodReturnType], @encode(double)) == 0) { | |
double returnValue; | |
[myInvocation getReturnValue:&returnValue]; | |
return JSValueMakeNumber(context, returnValue); | |
} else if (strcmp([mySignature methodReturnType], @encode(int)) == 0) { | |
int returnValue; | |
[myInvocation getReturnValue:&returnValue]; | |
return JSValueMakeNumber(context, returnValue); | |
} else if (strcmp([mySignature methodReturnType], @encode(BOOL)) == 0) { | |
BOOL returnValue; | |
[myInvocation getReturnValue:&returnValue]; | |
return JSValueMakeBoolean(context, returnValue); | |
} else if (strcmp([mySignature methodReturnType], "@\"NSString\"") == 0) { | |
NSString *returnValue; | |
[myInvocation getReturnValue:&returnValue]; | |
return JSValueMakeString(context, JSStringCreateWithUTF8CString([returnValue cStringUsingEncoding:NSUTF8StringEncoding])); | |
} else { | |
return NULL; | |
} | |
} | |
JSStringRef string = JSStringCreateWithUTF8CString("undefined"); | |
return JSValueMakeString(context, string); | |
} | |
JSObjectRef constructorCallback(JSContextRef ctx, JSObjectRef constructor, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception) { | |
return constructor; | |
} | |
@interface JSContext() | |
@property (nonatomic) JSContextRef context; | |
@property (nonatomic) JSObjectRef globalObj; | |
- (void)createContext; | |
- (void)createJSForObject:(id<JSObject>)object; | |
@end | |
@implementation JSContext | |
@synthesize context = _context; | |
@synthesize globalObj = _globalObj; | |
+ (JSContext *)sharedContext { | |
static dispatch_once_t once; | |
static JSContext *sharedContext; | |
dispatch_once(&once, ^ { sharedContext = [[self alloc] init]; }); | |
return sharedContext; | |
} | |
- (id)init { | |
self = [super init]; | |
if (self) { | |
[self createContext]; | |
} | |
return self; | |
} | |
#pragma mark - public methodes | |
- (void)addObject:(id<JSObject>)object { | |
[self createJSForObject:object]; | |
} | |
- (NSString *)evaluate:(NSString *)javascriptString { | |
JSStringRef scriptJS = JSStringCreateWithUTF8CString([javascriptString cStringUsingEncoding:NSUTF8StringEncoding]); | |
JSValueRef exception; | |
JSValueRef result = JSEvaluateScript(_context, scriptJS, _globalObj, 0, 0, &exception); | |
if(result == nil) { | |
return JSValueToNSString(_context, exception); | |
} else { | |
return JSValueToNSString(_context, result); | |
} | |
} | |
- (JSObjectCallAsFunctionCallback)callback { | |
return functionCallback; | |
} | |
#pragma mark - private methodes | |
- (void)createContext { | |
_context = JSGlobalContextCreate(NULL); | |
_globalObj = JSContextGetGlobalObject(_context); | |
} | |
- (void)createJSForObject:(id<JSObject>)objcObject{ | |
JSClassDefinition classDefinition = { | |
0, | |
kJSClassAttributeNone, | |
[[objcObject name] UTF8String], | |
NULL, | |
NULL, | |
[objcObject staticsMethodes], // JSStaticFunction | |
NULL, // init | |
NULL, // finalize | |
NULL, | |
getPropertyCallback, | |
setPropertyCallback, | |
NULL, | |
NULL, | |
functionCallback, | |
constructorCallback, | |
NULL, | |
NULL | |
}; | |
JSObjectRef object = JSObjectMake(_context, JSClassCreate(&classDefinition), NULL); | |
JSObjectSetPrivate(object, objcObject); | |
JSStringRef str = JSStringCreateWithUTF8CString([[objcObject name] UTF8String]); | |
JSObjectSetProperty(_context, _globalObj, str, object, kJSPropertyAttributeNone, NULL); | |
} | |
@end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#import "JSContext.h" | |
@interface Player : NSObject <JSObject> | |
@end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@implementation Player | |
- (NSString *)name { | |
return @"Player"; | |
} | |
- (SEL)selectorForJavascriptMethod:(NSString *)methodName { | |
if([methodName isEqualToString:@"drink"]) { | |
return @selector(drink); | |
} else if([methodName isEqualToString:@"jump"]) { | |
return @selector(jump); | |
} else if([methodName isEqualToString:@"shoot"]) { | |
return @selector(shoot); | |
} else if([methodName isEqualToString:@"hit"]) { | |
return @selector(hitWithDamage:); | |
} | |
return nil; | |
} | |
- (JSStaticFunction*)staticsMethodes { | |
static JSStaticFunction staticFunctions[] = { | |
{ "drink", [[JSContext sharedContext] callback], NULL }, | |
{ "jump", [[JSContext sharedContext] callback], NULL }, | |
{ "shoot", [[JSContext sharedContext] callback], NULL }, | |
{ "hit", [[JSContext sharedContext] callback], NULL }, | |
{ 0, 0, 0 } | |
}; | |
return staticFunctions; | |
} | |
@end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment