Skip to content

Instantly share code, notes, and snippets.

@hackfrag
Created November 13, 2011 18:56
Show Gist options
  • Save hackfrag/1362488 to your computer and use it in GitHub Desktop.
Save hackfrag/1362488 to your computer and use it in GitHub Desktop.
//
// 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
//
// 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
#import "JSContext.h"
@interface Player : NSObject <JSObject>
@end
@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