Files
cappuccino/Objective-J/Runtime.js
T
Martin Carlberg 81c02f24c6 New: msgSend will return undefined instead of nil when the receiver is undefined.
The msgSend function has always returned nil if the receiver has been nil or undefined.
Example:

var a = undefined;
var b = [a someSelector]; // b = nil

The variable b is now nil.

This pull request adds the ability for the msgSend function to return undefined if the receiver is undefined. If the receiver is nil it still return nil.
The above example again:

var a = undefined;
var b = [a someSelector]; // b = undefined

The variable b is now undefined.

Background.
The use of nil in Objective-C corresponds to the use of null in C and C++ and stands for "no value". A fundamental function in Objective-C is when you send a message to a receiver that is nil the result is again a nil value. This can be very convenient as you don't need the check if a receiver is nil before sending a message to it.

Objective-J adds the ability the use class structures and message send functionality for Javascript in the same way as Objective-C adds this to C and C++. Javascript also has the value null and is handled in the same way for Objective-J. Javascript also has the value undefined that stands for "not yet defined". This can sometimes be confusing and many try to handle null and undefined as the same thing. This is almost what Objective-J and the Cappuccino frameworks always has been doing. They try to always check for both null and ``undefined and always return nil but never undefined. We can say that Objective-J understands undefined but will try to translate it to nil.

Why do we need this change.
Javascript has both null and undefined and Objective-J is a superset of Javascript. It is now very hard to use undefined in Objective-J code as it will always try to translate it to nil. There are some Javascript libraries that use both null and undefined values and they are very hard to use from Objective-J programs today.  Same if you decide to use nil and undefined as a value in your own Objective-J code. This small change will make undefined a full member of the Objective-J language. It should be as it is a superset of Javascript. The Cappuccino frameworks will still handle undefined but only return nil.

How will it impact.
Today most methods will never return undefined so this is a very limited problem. If any does this the main concern is if some code will only check for nil and not undefined.
Example:

var a = [someObject someSelector];
If (a === nil) a = 42;
...

If the variable someObject contains undefined the variable a will now be set to undefined instead of nil.  But the variable a will then never be set to 42.
The correct way is to check for both nil and undefined like this:

var a = [someObject someSelector];
If (a == nil) a = 42;
...
2019-06-24 17:36:15 +02:00

1163 lines
32 KiB
JavaScript

/*
* Runtime.js
* Objective-J
*
* Created by Francisco Tolmasky.
* Copyright 2008-2010, 280 North, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
var CLS_CLASS = 0x1,
CLS_META = 0x2,
CLS_INITIALIZED = 0x4,
CLS_INITIALIZING = 0x8;
#define CHANGEINFO(aClass, aSetInfoMask, aClearInfoMask) aClass.info = (aClass.info | (aSetInfoMask)) & ~(aClearInfoMask)
#define GETINFO(aClass, anInfoMask) (aClass.info & (anInfoMask))
#define SETINFO(aClass, anInfoMask) CHANGEINFO(aClass, anInfoMask, 0)
#define CLEARINFO(aClass, anInfoMask) CHANGEINFO(aClass, 0, anInfoMask)
#define ISMETA(aClass) (GETINFO(aClass, CLS_META))
#define GETMETA(aClass) (ISMETA(aClass) ? aClass : aClass.isa)
#define ISINITIALIZED(aClass) GETINFO(GETMETA(aClass), CLS_INITIALIZED)
// MAXIMUM_RECURSION_CHECKS
// If defined, objj_msgSend will check for recursion deeper than MAXIMUM_RECURSION_DEPTH and
// throw an error if found. While crude, this can be helpful when your JavaScript debugger
// crashes on recursion errors (e.g. Safari) or ignores them (e.g. Chrome).
// #define MAXIMUM_RECURSION_CHECKS
#define MAXIMUM_RECURSION_DEPTH 80
GLOBAL(objj_ivar) = function(/*String*/ aName, /*String*/ aType)
{
this.name = aName;
this.type = aType;
}
GLOBAL(objj_method) = function(/*String*/ aName, /*IMP*/ anImplementation, /*Array<String>*/ types)
{
var method = anImplementation || function(/*id*/ aReceiver, /*SEL*/ aSelector) {CPException.isa.objj_msgSend2(CPException, "raise:reason:", CPInternalInconsistencyException, aReceiver.isa.method_msgSend0(self, "className") + " does not have an implementation for selector '" + aSelector + "'")};
method.method_name = aName;
method.method_imp = anImplementation;
method.method_types = types;
return method;
}
GLOBAL(objj_class) = function(displayName)
{
this.isa = NULL;
this.version = 0;
this.super_class = NULL;
this.name = NULL;
this.info = 0;
this.ivar_list = [];
this.ivar_store = function() { };
this.ivar_dtable = this.ivar_store.prototype;
this.method_list = [];
this.method_store = function() { };
this.method_dtable = this.method_store.prototype;
this.protocol_list = [];
#if DEBUG
// Naming the allocator allows the WebKit heap snapshot tool to display object class names correctly
// HACK: displayName property is not respected so we must eval a function to name it
eval("this.allocator = function " + (displayName || "OBJJ_OBJECT").replace(/\W/g, "_") + "() { }");
#else
this.allocator = function() { };
#endif
this._UID = -1;
}
GLOBAL(objj_protocol) = function(/*String*/ aName)
{
this.name = aName;
this.instance_methods = { };
this.class_methods = { };
}
GLOBAL(objj_object) = function()
{
this.isa = NULL;
this._UID = -1;
}
GLOBAL(objj_typeDef) = function(/*String*/ aName)
{
this.name = aName;
}
// Working with Classes
GLOBAL(class_getName) = function(/*Class*/ aClass)
{
if (aClass == Nil)
return "";
return aClass.name;
}
GLOBAL(class_isMetaClass) = function(/*Class*/ aClass)
{
if (!aClass)
return NO;
return ISMETA(aClass);
}
GLOBAL(class_getSuperclass) = function(/*Class*/ aClass)
{
if (aClass == Nil)
return Nil;
return aClass.super_class;
}
GLOBAL(class_setSuperclass) = function(/*Class*/ aClass, /*Class*/ aSuperClass)
{
// Set up the actual class hierarchy.
aClass.super_class = aSuperClass;
aClass.isa.super_class = aSuperClass.isa;
}
GLOBAL(class_addIvar) = function(/*Class*/ aClass, /*String*/ aName, /*String*/ aType)
{
var thePrototype = aClass.allocator.prototype;
// FIXME: Use getInstanceVariable
if (typeof thePrototype[aName] != "undefined")
return NO;
var ivar = new objj_ivar(aName, aType);
aClass.ivar_list.push(ivar);
aClass.ivar_dtable[aName] = ivar;
thePrototype[aName] = NULL;
return YES;
}
GLOBAL(class_addIvars) = function(/*Class*/ aClass, /*Array*/ivars)
{
var index = 0,
count = ivars.length,
thePrototype = aClass.allocator.prototype;
for (; index < count; ++index)
{
var ivar = ivars[index],
name = ivar.name;
// FIXME: Use getInstanceVariable
if (typeof thePrototype[name] === "undefined")
{
aClass.ivar_list.push(ivar);
aClass.ivar_dtable[name] = ivar;
thePrototype[name] = NULL;
}
}
}
GLOBAL(class_copyIvarList) = function(/*Class*/ aClass)
{
return aClass.ivar_list.slice(0);
}
//#define class_copyIvarList(aClass) (aClass.ivar_list.slice(0))
#define METHOD_DISPLAY_NAME(aClass, aMethod) (ISMETA(aClass) ? '+' : '-') + " [" + class_getName(aClass) + ' ' + method_getName(aMethod) + ']'
GLOBAL(class_addMethod) = function(/*Class*/ aClass, /*SEL*/ aName, /*IMP*/ anImplementation, /*Array<String>*/ types)
{
// FIXME: return NO if it exists?
var method = new objj_method(aName, anImplementation, types);
aClass.method_list.push(method);
aClass.method_dtable[aName] = method;
#if DEBUG
// Give this function a "pretty" name for the console.
method.displayName = METHOD_DISPLAY_NAME(aClass, method);
#endif
// FIXME: Should this be done here?
// If this is a root class...
if (!ISMETA(aClass) && GETMETA(aClass).isa === GETMETA(aClass))
class_addMethod(GETMETA(aClass), aName, anImplementation, types);
return YES;
}
GLOBAL(class_addMethods) = function(/*Class*/ aClass, /*Array*/ methods)
{
var index = 0,
count = methods.length,
method_list = aClass.method_list,
method_dtable = aClass.method_dtable;
for (; index < count; ++index)
{
var method = methods[index];
// FIXME: Don't do it if it exists?
method_list.push(method);
method_dtable[method.method_name] = method;
#if DEBUG
// Give this function a "pretty" name for the console.
method.displayName = METHOD_DISPLAY_NAME(aClass, method);
#endif
}
// If this is a root class...
if (!ISMETA(aClass) && GETMETA(aClass).isa === GETMETA(aClass))
class_addMethods(GETMETA(aClass), methods);
}
GLOBAL(class_getInstanceMethod) = function(/*Class*/ aClass, /*SEL*/ aSelector)
{
if (!aClass || !aSelector)
return NULL;
var method = aClass.method_dtable[aSelector];
return method ? method : NULL;
}
GLOBAL(class_getInstanceVariable) = function(/*Class*/ aClass, /*String*/ aName)
{
if (!aClass || !aName)
return NULL;
// FIXME: this doesn't appropriately deal with Object's properties.
var variable = aClass.ivar_dtable[aName];
return variable;
}
GLOBAL(class_getClassMethod) = function(/*Class*/ aClass, /*SEL*/ aSelector)
{
if (!aClass || !aSelector)
return NULL;
var method = GETMETA(aClass).method_dtable[aSelector];
return method ? method : NULL;
}
GLOBAL(class_respondsToSelector) = function(/*Class*/ aClass, /*SEL*/ aSelector)
{
return class_getClassMethod(aClass, aSelector) != NULL;
}
GLOBAL(class_copyMethodList) = function(/*Class*/ aClass)
{
return aClass.method_list.slice(0);
}
GLOBAL(class_getVersion) = function(/*Class*/ aClass)
{
return aClass.version;
}
GLOBAL(class_setVersion) = function(/*Class*/ aClass, /*Integer*/ aVersion)
{
aClass.version = parseInt(aVersion, 10);
}
GLOBAL(class_replaceMethod) = function(/*Class*/ aClass, /*SEL*/ aSelector, /*IMP*/ aMethodImplementation)
{
if (!aClass || !aSelector)
return NULL;
var method = aClass.method_dtable[aSelector],
method_imp = method.method_imp,
new_method = new objj_method(method.method_name, aMethodImplementation, method.method_types);
new_method.displayName = method.displayName;
aClass.method_dtable[aSelector] = new_method;
var index = aClass.method_list.indexOf(method);
if (index !== -1)
{
aClass.method_list[index] = new_method;
}
else
{
aClass.method_list.push(new_method);
}
return method_imp;
}
GLOBAL(class_addProtocol) = function(/*Class*/ aClass, /*Protocol*/ aProtocol)
{
if (!aProtocol || class_conformsToProtocol(aClass, aProtocol))
{
return;
}
(aClass.protocol_list || (aClass.protocol_list = [])).push(aProtocol);
return true;
}
GLOBAL(class_conformsToProtocol) = function(/*Class*/ aClass, /*Protocol*/ aProtocol)
{
if (!aProtocol)
return false;
while (aClass)
{
var protocols = aClass.protocol_list,
size = protocols ? protocols.length : 0;
for (var i = 0; i < size; i++)
{
var p = protocols[i];
if (p.name === aProtocol.name)
{
return true;
}
if (protocol_conformsToProtocol(p, aProtocol))
{
return true;
}
}
aClass = class_getSuperclass(aClass);
}
return false;
}
GLOBAL(class_copyProtocolList) = function(/*Class*/ aClass)
{
var protocols = aClass.protocol_list;
return protocols ? protocols.slice(0) : [];
}
GLOBAL(protocol_conformsToProtocol) = function(/*Protocol*/ p1, /*Protocol*/ p2)
{
if (!p1 || !p2)
return false;
if (p1.name === p2.name)
return true;
var protocols = p1.protocol_list,
size = protocols ? protocols.length : 0;
for (var i = 0; i < size; i++)
{
var p = protocols[i];
if (p.name === p2.name)
{
return true;
}
if (protocol_conformsToProtocol(p, p2))
{
return true;
}
}
return false;
}
var REGISTERED_PROTOCOLS = Object.create(null);
GLOBAL(objj_allocateProtocol) = function(/*String*/ aName)
{
var protocol = new objj_protocol(aName);
return protocol;
}
GLOBAL(objj_registerProtocol) = function(/*Protocol*/ proto)
{
REGISTERED_PROTOCOLS[proto.name] = proto;
}
GLOBAL(protocol_getName) = function(/*Protocol*/ proto)
{
return proto.name;
}
// Right now we only register required methods. THis might need to change in the future
GLOBAL(protocol_addMethodDescription) = function(/*Protocol*/ proto, /*SEL*/ selector, /*Array*/ types, /*BOOL*/ isRequiredMethod, /*BOOL*/ isInstanceMethod)
{
if (!proto || !selector) return;
if (isRequiredMethod)
(isInstanceMethod ? proto.instance_methods : proto.class_methods)[selector] = new objj_method(selector, null, types);
}
GLOBAL(protocol_addMethodDescriptions) = function(/*Protocol*/ proto, /*Array*/ methods, /*BOOL*/ isRequiredMethod, /*BOOL*/ isInstanceMethod)
{
if (!isRequiredMethod) return;
var index = 0,
count = methods.length,
method_dtable = isInstanceMethod ? proto.instance_methods : proto.class_methods;
for (; index < count; ++index)
{
var method = methods[index];
method_dtable[method.method_name] = method;
}
}
GLOBAL(protocol_copyMethodDescriptionList) = function(/*Protocol*/ proto, /*BOOL*/ isRequiredMethod, /*BOOL*/ isInstanceMethod)
{
if (!isRequiredMethod)
return [];
var method_dtable = isInstanceMethod ? proto.instance_methods : proto.class_methods,
methodList = [];
for (var selector in method_dtable)
if (method_dtable.hasOwnProperty(selector))
methodList.push(method_dtable[selector]);
return methodList;
}
GLOBAL(protocol_addProtocol) = function(/*Protocol*/ proto, /*Protocol*/ addition)
{
if (!proto || !addition) return;
(proto.protocol_list || (proto.protocol_list = [])).push(addition);
}
var REGISTERED_TYPEDEFS = Object.create(null);
GLOBAL(objj_allocateTypeDef) = function(/*String*/ aName)
{
var typeDef = new objj_typeDef(aName);
return typeDef;
}
GLOBAL(objj_registerTypeDef) = function(/*TypeDef*/ typeDef)
{
REGISTERED_TYPEDEFS[typeDef.name] = typeDef;
}
GLOBAL(typeDef_getName) = function(/*TypeDef*/ typeDef)
{
return typeDef.name;
}
var _class_initialize = function(/*Class*/ aClass)
{
var meta = GETMETA(aClass);
if (GETINFO(aClass, CLS_META))
aClass = objj_getClass(aClass.name);
if (aClass.super_class && !ISINITIALIZED(aClass.super_class))
_class_initialize(aClass.super_class);
if (!GETINFO(meta, CLS_INITIALIZED) && !GETINFO(meta, CLS_INITIALIZING))
{
SETINFO(meta, CLS_INITIALIZING);
// We don't need to initialize any more.
aClass.objj_msgSend = objj_msgSendFast;
aClass.objj_msgSend0 = objj_msgSendFast0;
aClass.objj_msgSend1 = objj_msgSendFast1;
aClass.objj_msgSend2 = objj_msgSendFast2;
aClass.objj_msgSend3 = objj_msgSendFast3;
meta.objj_msgSend = objj_msgSendFast;
meta.objj_msgSend0 = objj_msgSendFast0;
meta.objj_msgSend1 = objj_msgSendFast1;
meta.objj_msgSend2 = objj_msgSendFast2;
meta.objj_msgSend3 = objj_msgSendFast3;
aClass.method_msgSend = aClass.method_dtable;
var metaMethodDTable = meta.method_msgSend = meta.method_dtable,
initializeImp = metaMethodDTable["initialize"];
if (initializeImp)
initializeImp(aClass, "initialize");
CHANGEINFO(meta, CLS_INITIALIZED, CLS_INITIALIZING);
}
}
GLOBAL(_objj_forward) = function(self, _cmd)
{
var isa = self.isa,
meta = GETMETA(isa);
if (!GETINFO(meta, CLS_INITIALIZED) && !GETINFO(meta, CLS_INITIALIZING))
{
_class_initialize(isa);
}
var implementation = isa.method_msgSend[_cmd];
if (implementation)
{
return implementation.apply(isa, arguments);
}
implementation = isa.method_dtable[SEL_forwardingTargetForSelector_];
if (implementation)
{
var target = implementation(self, SEL_forwardingTargetForSelector_, _cmd);
if (target && target !== self)
{
arguments[0] = target;
return target.isa.objj_msgSend.apply(target.isa, arguments);
}
}
implementation = isa.method_dtable[SEL_methodSignatureForSelector_];
if (implementation)
{
var forwardInvocationImplementation = isa.method_dtable[SEL_forwardInvocation_];
if (forwardInvocationImplementation)
{
var signature = implementation(self, SEL_methodSignatureForSelector_, _cmd);
if (signature)
{
var invocationClass = objj_lookUpClass("CPInvocation");
if (invocationClass)
{
var invocation = invocationClass.isa.objj_msgSend1(invocationClass, SEL_invocationWithMethodSignature_, signature),
index = 0,
count = arguments.length;
if (invocation != null) {
var invocationIsa = invocation.isa;
for (; index < count; ++index)
invocationIsa.objj_msgSend2(invocation, SEL_setArgument_atIndex_, arguments[index], index);
}
forwardInvocationImplementation(self, SEL_forwardInvocation_, invocation);
return invocation == null ? null : invocationIsa.objj_msgSend0(invocation, SEL_returnValue);
}
}
}
}
implementation = isa.method_dtable[SEL_doesNotRecognizeSelector_];
if (implementation)
return implementation(self, SEL_doesNotRecognizeSelector_, _cmd);
throw class_getName(isa) + " does not implement doesNotRecognizeSelector: when sending " + sel_getName(_cmd) + ". Did you forget a superclass for " + class_getName(isa) + "?";
};
// I think this forward:: may need to be a common method, instead of defined in CPObject.
#define CLASS_GET_METHOD_IMPLEMENTATION(aMethodImplementation, aClass, aSelector)\
if (!ISINITIALIZED(aClass))\
_class_initialize(aClass);\
\
aMethodImplementation = aClass.method_dtable[aSelector] || _objj_forward;
GLOBAL(class_getMethodImplementation) = function(/*Class*/ aClass, /*SEL*/ aSelector)
{
CLASS_GET_METHOD_IMPLEMENTATION(var implementation, aClass, aSelector);
return implementation;
}
// Adding Classes
var REGISTERED_CLASSES = Object.create(null);
GLOBAL(objj_enumerateClassesUsingBlock) = function(/* function(aClass) */aBlock)
{
for (var key in REGISTERED_CLASSES)
{
aBlock(REGISTERED_CLASSES[key]);
}
}
GLOBAL(objj_allocateClassPair) = function(/*Class*/ superclass, /*String*/ aName)
{
var classObject = new objj_class(aName),
metaClassObject = new objj_class(aName),
rootClassObject = classObject;
// If we don't have a superclass, we are the root class.
if (superclass)
{
rootClassObject = superclass;
while (rootClassObject.superclass)
rootClassObject = rootClassObject.superclass;
// Give our current allocator all the instance variables of our super class' allocator.
classObject.allocator.prototype = new superclass.allocator;
// "Inherit" parent properties.
classObject.ivar_dtable = classObject.ivar_store.prototype = new superclass.ivar_store;
classObject.method_dtable = classObject.method_store.prototype = new superclass.method_store;
metaClassObject.method_dtable = metaClassObject.method_store.prototype = new superclass.isa.method_store;
// Set up the actual class hierarchy.
classObject.super_class = superclass;
metaClassObject.super_class = superclass.isa;
}
else
classObject.allocator.prototype = new objj_object();
classObject.isa = metaClassObject;
classObject.name = aName;
classObject.info = CLS_CLASS;
classObject._UID = objj_generateObjectUID();
// It needs initialize
classObject.init = true;
metaClassObject.isa = rootClassObject.isa;
metaClassObject.name = aName;
metaClassObject.info = CLS_META;
metaClassObject._UID = objj_generateObjectUID();
// It needs initialize
metaClassObject.init = true;
return classObject;
}
var CONTEXT_BUNDLE = nil;
GLOBAL(objj_registerClassPair) = function(/*Class*/ aClass)
{
global[aClass.name] = aClass;
REGISTERED_CLASSES[aClass.name] = aClass;
addClassToBundle(aClass, CONTEXT_BUNDLE);
}
GLOBAL(objj_resetRegisterClasses) = function()
{
for (var key in REGISTERED_CLASSES)
delete global[key];
REGISTERED_CLASSES = Object.create(null);
REGISTERED_PROTOCOLS = Object.create(null);
REGISTERED_TYPEDEFS = Object.create(null);
resetBundle();
}
// Instantiating Classes
GLOBAL(class_createInstance) = function(/*Class*/ aClass)
{
if (!aClass)
throw new Error("*** Attempting to create object with Nil class.");
var object = new aClass.allocator();
object.isa = aClass;
object._UID = objj_generateObjectUID();
return object;
}
// Opera 9.5.1 has a bug where prototypes "inheret" members from instances when "with" is used.
// Given that the Opera team is so fond of bug-testing instead of version-testing, we'll go
// ahead and do that.
var prototype_bug = function() { }
prototype_bug.prototype.member = false;
with (new prototype_bug())
member = true;
// If the bug exists, go down the slow path.
if (new prototype_bug().member)
{
var fast_class_createInstance = class_createInstance;
class_createInstance = function(/*Class*/ aClass)
{
var object = fast_class_createInstance(aClass);
if (object)
{
var theClass = object.isa,
actualClass = theClass;
while (theClass)
{
var ivars = theClass.ivar_list,
count = ivars.length;
while (count--)
object[ivars[count].name] = NULL;
theClass = theClass.super_class;
}
object.isa = actualClass;
}
return object;
}
}
// Working with Instances
GLOBAL(object_getClassName) = function(/*id*/ anObject)
{
if (!anObject)
return "";
var theClass = anObject.isa;
return theClass ? class_getName(theClass) : "";
}
//objc_getClassList
GLOBAL(objj_lookUpClass) = function(/*String*/ aName)
{
var theClass = REGISTERED_CLASSES[aName];
return theClass ? theClass : Nil;
}
GLOBAL(objj_getClass) = function(/*String*/ aName)
{
var theClass = REGISTERED_CLASSES[aName];
/*if (!theClass)
{
for (var key in REGISTERED_CLASSES)
{
print("regClass: " + key + ", regClass.isa: " + REGISTERED_CLASSES[key].isa);
}
print("");
}*/
if (!theClass)
{
// class handler callback???
}
return theClass ? theClass : Nil;
}
GLOBAL(objj_getClassList) = function(/*CPArray*/ buffer, /*int*/ bufferLen)
{
for (var aName in REGISTERED_CLASSES)
{
buffer.push(REGISTERED_CLASSES[aName]);
if (bufferLen && --bufferLen === 0)
break;
}
return buffer.length;
}
//objc_getRequiredClass
GLOBAL(objj_getMetaClass) = function(/*String*/ aName)
{
var theClass = objj_getClass(aName);
return GETMETA(theClass);
}
// Working with Protocol
GLOBAL(objj_getProtocol) = function(/*String*/ aName)
{
return REGISTERED_PROTOCOLS[aName];
}
// Working with typeDef
GLOBAL(objj_getTypeDef) = function(/*String*/ aName)
{
return REGISTERED_TYPEDEFS[aName];
}
// Working with Instance Variables
GLOBAL(ivar_getName) = function(anIvar)
{
return anIvar.name;
}
GLOBAL(ivar_getTypeEncoding) = function(anIvar)
{
return anIvar.type;
}
// Sending Messages
#ifdef MAXIMUM_RECURSION_CHECKS
var __objj_msgSend__StackDepth = 0;
#endif
GLOBAL(objj_msgSend) = function(/*id*/ aReceiver, /*SEL*/ aSelector)
{
if (aReceiver == nil)
return aReceiver;
var isa = aReceiver.isa;
// Here we should do the following line: CLASS_GET_METHOD_IMPLEMENTATION(var implementation, isa, aSelector);
// But set the 'init' attribute to 'true' when register the class pair and just check for that here is around 20% faster depending
// on environment. The '_class_initialize' function sets the 'init' attribute to 'false' when the class is initialized
if (isa.init)
_class_initialize(isa);
var method = isa.method_dtable[aSelector];
var implementation = method ? method.method_imp : _objj_forward;
#ifdef MAXIMUM_RECURSION_CHECKS
if (__objj_msgSend__StackDepth++ > MAXIMUM_RECURSION_DEPTH)
throw new Error("Maximum call stack depth exceeded.");
try {
#endif
switch(arguments.length)
{
case 2: return implementation(aReceiver, aSelector);
case 3: return implementation(aReceiver, aSelector, arguments[2]);
case 4: return implementation(aReceiver, aSelector, arguments[2], arguments[3]);
case 5: return implementation(aReceiver, aSelector, arguments[2], arguments[3], arguments[4]);
case 6: return implementation(aReceiver, aSelector, arguments[2], arguments[3], arguments[4], arguments[5]);
case 7: return implementation(aReceiver, aSelector, arguments[2], arguments[3], arguments[4], arguments[5], arguments[6]);
}
return implementation.apply(aReceiver, arguments);
#ifdef MAXIMUM_RECURSION_CHECKS
} finally {
__objj_msgSend__StackDepth--;
}
#endif
}
GLOBAL(objj_msgSendSuper) = function(/*id*/ aSuper, /*SEL*/ aSelector)
{
var super_class = aSuper.super_class;
arguments[0] = aSuper.receiver;
CLASS_GET_METHOD_IMPLEMENTATION(var implementation, super_class, aSelector);
return implementation.apply(aSuper.receiver, arguments);
}
GLOBAL(objj_msgSendSuper0) = function(/*id*/ aSuper, /*SEL*/ aSelector)
{
return (aSuper.super_class.method_dtable[aSelector] || _objj_forward)(aSuper.receiver, aSelector);
}
GLOBAL(objj_msgSendSuper1) = function(/*id*/ aSuper, /*SEL*/ aSelector, arg0)
{
return (aSuper.super_class.method_dtable[aSelector] || _objj_forward)(aSuper.receiver, aSelector, arg0);
}
GLOBAL(objj_msgSendSuper2) = function(/*id*/ aSuper, /*SEL*/ aSelector, arg0, arg1)
{
return (aSuper.super_class.method_dtable[aSelector] || _objj_forward)(aSuper.receiver, aSelector, arg0, arg1);
}
GLOBAL(objj_msgSendSuper3) = function(/*id*/ aSuper, /*SEL*/ aSelector, arg0, arg1, arg2)
{
return (aSuper.super_class.method_dtable[aSelector] || _objj_forward)(aSuper.receiver, aSelector, arg0, arg1, arg2);
}
GLOBAL(objj_msgSendFast) = function(/*id*/ aReceiver, /*SEL*/ aSelector)
{
#ifdef MAXIMUM_RECURSION_CHECKS
if (__objj_msgSend__StackDepth++ > MAXIMUM_RECURSION_DEPTH)
throw new Error("Maximum call stack depth exceeded.");
try {
#endif
return (this.method_dtable[aSelector] || _objj_forward).apply(aReceiver, arguments);
#ifdef MAXIMUM_RECURSION_CHECKS
} finally {
__objj_msgSend__StackDepth--;
}
#endif
}
var objj_msgSendFastInitialize = function(/*id*/ aReceiver, /*SEL*/ aSelector)
{
_class_initialize(this);
return this.objj_msgSend.apply(this, arguments);
}
GLOBAL(objj_msgSendFast0) = function(/*id*/ aReceiver, /*SEL*/ aSelector)
{
#ifdef MAXIMUM_RECURSION_CHECKS
if (__objj_msgSend__StackDepth++ > MAXIMUM_RECURSION_DEPTH)
throw new Error("Maximum call stack depth exceeded.");
try {
#endif
return (this.method_dtable[aSelector] || _objj_forward)(aReceiver, aSelector);
#ifdef MAXIMUM_RECURSION_CHECKS
} finally {
__objj_msgSend__StackDepth--;
}
#endif
}
var objj_msgSendFast0Initialize = function(/*id*/ aReceiver, /*SEL*/ aSelector)
{
_class_initialize(this);
return this.objj_msgSend0(aReceiver, aSelector);
}
GLOBAL(objj_msgSendFast1) = function(/*id*/ aReceiver, /*SEL*/ aSelector, arg0)
{
#ifdef MAXIMUM_RECURSION_CHECKS
if (__objj_msgSend__StackDepth++ > MAXIMUM_RECURSION_DEPTH)
throw new Error("Maximum call stack depth exceeded.");
try {
#endif
return (this.method_dtable[aSelector] || _objj_forward)(aReceiver, aSelector, arg0);
#ifdef MAXIMUM_RECURSION_CHECKS
} finally {
__objj_msgSend__StackDepth--;
}
#endif
}
var objj_msgSendFast1Initialize = function(/*id*/ aReceiver, /*SEL*/ aSelector, arg0)
{
_class_initialize(this);
return this.objj_msgSend1(aReceiver, aSelector, arg0);
}
GLOBAL(objj_msgSendFast2) = function(/*id*/ aReceiver, /*SEL*/ aSelector, arg0, arg1)
{
#ifdef MAXIMUM_RECURSION_CHECKS
if (__objj_msgSend__StackDepth++ > MAXIMUM_RECURSION_DEPTH)
throw new Error("Maximum call stack depth exceeded.");
try {
#endif
return (this.method_dtable[aSelector] || _objj_forward)(aReceiver, aSelector, arg0, arg1);
#ifdef MAXIMUM_RECURSION_CHECKS
} finally {
__objj_msgSend__StackDepth--;
}
#endif
}
var objj_msgSendFast2Initialize = function(/*id*/ aReceiver, /*SEL*/ aSelector, arg0, arg1)
{
_class_initialize(this);
return this.objj_msgSend2(aReceiver, aSelector, arg0, arg1);
}
GLOBAL(objj_msgSendFast3) = function(/*id*/ aReceiver, /*SEL*/ aSelector, arg0, arg1, arg2)
{
#ifdef MAXIMUM_RECURSION_CHECKS
if (__objj_msgSend__StackDepth++ > MAXIMUM_RECURSION_DEPTH)
throw new Error("Maximum call stack depth exceeded.");
try {
#endif
return (this.method_dtable[aSelector] || _objj_forward)(aReceiver, aSelector, arg0, arg1, arg2);
#ifdef MAXIMUM_RECURSION_CHECKS
} finally {
__objj_msgSend__StackDepth--;
}
#endif
}
var objj_msgSendFast3Initialize = function(/*id*/ aReceiver, /*SEL*/ aSelector, arg0, arg1, arg2)
{
_class_initialize(this);
return this.objj_msgSend3(aReceiver, aSelector, arg0, arg1, arg2);
}
// Working with Methods
GLOBAL(method_getName) = function(/*Method*/ aMethod)
{
return aMethod.method_name;
}
// This will not return correct values if the compiler does not have the option 'IncludeTypeSignatures'
GLOBAL(method_copyReturnType) = function(/*Method*/ aMethod)
{
var types = aMethod.method_types;
if (types)
{
var argType = types[0];
return argType != NULL ? argType : NULL;
}
else
return NULL;
}
// This will not return correct values for index > 1 if the compiler does not have the option 'IncludeTypeSignatures'
GLOBAL(method_copyArgumentType) = function(/*Method*/ aMethod, /*unsigned int*/ index)
{
switch (index) {
case 0:
return "id";
case 1:
return "SEL";
default:
var types = aMethod.method_types;
if (types)
{
var argType = types[index - 1];
return argType != NULL ? argType : NULL;
}
else
return NULL;
}
}
// Returns number of arguments for a method. The first argument is 'self' and the second is the selector.
// Those are followed by the method arguments. So for example it will return 2 for a method with no arguments.
GLOBAL(method_getNumberOfArguments) = function(/*Method*/ aMethod)
{
var types = aMethod.method_types;
return types ? types.length + 1 : ((aMethod.method_name.match(/:/g) || []).length + 2);
}
GLOBAL(method_getImplementation) = function(/*Method*/ aMethod)
{
return aMethod.method_imp;
}
GLOBAL(method_setImplementation) = function(/*Method*/ aMethod, /*IMP*/ anImplementation)
{
var oldImplementation = aMethod.method_imp;
aMethod.method_imp = anImplementation;
return oldImplementation;
}
GLOBAL(method_exchangeImplementations) = function(/*Method*/ lhs, /*Method*/ rhs)
{
var lhs_imp = method_getImplementation(lhs),
rhs_imp = method_getImplementation(rhs);
method_setImplementation(lhs, rhs_imp);
method_setImplementation(rhs, lhs_imp);
}
// Working with Selectors
GLOBAL(sel_getName) = function(aSelector)
{
return aSelector ? aSelector : "<null selector>";
}
GLOBAL(sel_getUid) = function(/*String*/ aName)
{
return aName;
}
GLOBAL(sel_isEqual) = function(/*SEL*/ lhs, /*SEL*/ rhs)
{
return lhs === rhs;
}
GLOBAL(sel_registerName) = function(/*String*/ aName)
{
return aName;
}
objj_class.prototype.toString = objj_object.prototype.toString = function()
{
var isa = this.isa;
if (class_getInstanceMethod(isa, SEL_description))
return isa.objj_msgSend0(this, SEL_description);
if (class_isMetaClass(isa))
return this.name;
return "[" + isa.name + " Object](-description not implemented)";
}
objj_class.prototype.objj_msgSend = objj_msgSendFastInitialize;
objj_class.prototype.objj_msgSend0 = objj_msgSendFast0Initialize;
objj_class.prototype.objj_msgSend1 = objj_msgSendFast1Initialize;
objj_class.prototype.objj_msgSend2 = objj_msgSendFast2Initialize;
objj_class.prototype.objj_msgSend3 = objj_msgSendFast3Initialize;
objj_class.prototype.method_msgSend = Object.create(null);
var SEL_description = sel_getUid("description"),
SEL_forwardingTargetForSelector_ = sel_getUid("forwardingTargetForSelector:"),
SEL_methodSignatureForSelector_ = sel_getUid("methodSignatureForSelector:"),
SEL_forwardInvocation_ = sel_getUid("forwardInvocation:"),
SEL_doesNotRecognizeSelector_ = sel_getUid("doesNotRecognizeSelector:"),
SEL_invocationWithMethodSignature_ = sel_getUid("invocationWithMethodSignature:"),
SEL_setTarget_ = sel_getUid("setTarget:"),
SEL_setSelector_ = sel_getUid("setSelector:"),
SEL_setArgument_atIndex_ = sel_getUid("setArgument:atIndex:"),
SEL_returnValue = sel_getUid("returnValue");