diff --git a/Foundation/CPObject.j b/Foundation/CPObject.j index bc84e9ce8..72b877c9f 100644 --- a/Foundation/CPObject.j +++ b/Foundation/CPObject.j @@ -254,6 +254,26 @@ CPLog(@"Got some class: %@", inst); return NO; } +/*! + Test whether instances of this class conforms to the provided protocol. + @param aProtocol the protocol for which to test the class + @return \c YES if instances of the class conforms to the protocol +*/ ++ (BOOL)conformsToProtocol:(Protocol)aProtocol +{ + return class_conformsToProtocol(self, aProtocol); +} + +/*! + Tests whether the receiver conforms to the provided protocol. + @param protocol the protocol for which to test the class + @return \c YES if instances of the class conforms to the protocol +*/ +- (BOOL)conformsToProtocol:(Protocol)aProtocol +{ + return class_conformsToProtocol(isa, aProtocol); +} + // Obtaining method information /*! diff --git a/Objective-J/ObjJAcornCompiler.js b/Objective-J/ObjJAcornCompiler.js index 9bdad14a3..3023ac2be 100644 --- a/Objective-J/ObjJAcornCompiler.js +++ b/Objective-J/ObjJAcornCompiler.js @@ -45,7 +45,12 @@ Scope.prototype.isRootScope = function() Scope.prototype.currentClassName = function() { - return this.classDef ? this.classDef.className : this.prev ? this.prev.currentClassName() : null; + return this.classDef ? this.classDef.name : this.prev ? this.prev.currentClassName() : null; +} + +Scope.prototype.currentProtocolName = function() +{ + return this.protocolDef ? this.protocolDef.name : this.prev ? this.prev.currentProtocolName() : null; } Scope.prototype.getIvarForCurrentClass = function(/* String */ ivarName) @@ -144,6 +149,215 @@ StringBuffer.prototype.isEmpty = function() return this.atoms.length !== 0; } +// Both the ClassDef and ProtocolDef conforms to a 'protocol' (That we can't declare in Javascript). +// Both Objects have the attribute 'protocols': Array of ProtocolDef that they conform to +// Both also have the functions: addInstanceMethod, addClassMethod, getInstanceMethod and getClassMethod +// classDef = {"className": aClassName, "superClass": superClass , "ivars": myIvars, "instanceMethods": instanceMethodDefs, "classMethods": classMethodDefs, "protocols": myProtocols}; +var ClassDef = function(isImplementationDeclaration, name, superClass, ivars, instanceMethods, classMethods, protocols) +{ + this.name = name; + if (superClass) + this.superClass = superClass; + if (ivars) + this.ivars = ivars; + if (isImplementationDeclaration) { + this.instanceMethods = instanceMethods || Object.create(null); + this.classMethods = classMethods || Object.create(null); + } + if (protocols) + this.protocols = protocols; +} + +ClassDef.prototype.addInstanceMethod = function(methodDef) { + this.instanceMethods[methodDef.name] = methodDef; +} + +ClassDef.prototype.addClassMethod = function(methodDef) { + this.classMethods[methodDef.name] = methodDef; +} + +ClassDef.prototype.listOfNotImplementedMethodsForProtocols = function(protocolDefs) { + var resultList = [], + instanceMethods = this.getInstanceMethods(), + classMethods = this.getClassMethods(); + + for (var i = 0, size = protocolDefs.length; i < size; i++) + { + var protocolDef = protocolDefs[i], + protocolInstanceMethods = protocolDef.requiredInstanceMethods, + protocolClassMethods = protocolDef.requiredClassMethods, + inheritFromProtocols = protocolDef.protocols; + + if (protocolInstanceMethods) for (var methodName in protocolInstanceMethods) { + var methodDef = protocolInstanceMethods[methodName]; + + if (!instanceMethods[methodName]) + resultList.push({"methodDef": methodDef, "protocolDef": protocolDef}); + } + + if (protocolClassMethods) for (var methodName in protocolClassMethods) { + var methodDef = protocolClassMethods[methodName]; + + if (!classMethods[methodName]) + resultList.push({"methodDef": methodDef, "protocolDef": protocolDef}); + } + + if (inheritFromProtocols) + resultList = resultList.concat(this.listOfNotImplementedMethodsForProtocols(inheritFromProtocols)); + } + + return resultList; +} + +ClassDef.prototype.getInstanceMethod = function(name) { + var instanceMethods = this.instanceMethods; + + if (instanceMethods) { + var method = instanceMethods[name]; + + if (method) + return method; + } + + var superClass = this.superClass; + + if (superClass) + return superClass.getInstanceMethod(name); + + return null; +} + +ClassDef.prototype.getClassMethod = function(name) { + var classMethods = this.classMethods; + if (classMethods) { + var method = classMethods[name]; + + if (method) + return method; + } + + var superClass = this.superClass; + + if (superClass) + return superClass.getClassMethod(name); + + return null; +} + +// Return a new Array with all instance methods +ClassDef.prototype.getInstanceMethods = function() { + var instanceMethods = this.instanceMethods; + if (instanceMethods) { + var superClass = this.superClass, + returnObject = Object.create(null); + if (superClass) { + var superClassMethods = superClass.getInstanceMethods(); + for (var methodName in superClassMethods) + returnObject[methodName] = superClassMethods[methodName]; + } + + for (var methodName in instanceMethods) + returnObject[methodName] = instanceMethods[methodName]; + + return returnObject; + } + + return []; +} + +// Return a new Array with all class methods +ClassDef.prototype.getClassMethods = function() { + var classMethods = this.classMethods; + if (classMethods) { + var superClass = this.superClass, + returnObject = Object.create(null); + if (superClass) { + var superClassMethods = superClass.getClassMethods(); + for (var methodName in superClassMethods) + returnObject[methodName] = superClassMethods[methodName]; + } + + for (var methodName in classMethods) + returnObject[methodName] = classMethods[methodName]; + + return returnObject; + } + + return []; +} + +// protocolDef = {"name": aProtocolName, "protocols": inheritFromProtocols, "requiredInstanceMethods": requiredInstanceMethodDefs, "requiredClassMethods": requiredClassMethodDefs}; +var ProtocolDef = function(name, protocols, requiredInstanceMethodDefs, requiredClassMethodDefs) +{ + this.name = name; + this.protocols = protocols; + if (requiredInstanceMethodDefs) + this.requiredInstanceMethods = requiredInstanceMethodDefs; + if (requiredClassMethodDefs) + this.requiredClassMethods = requiredClassMethodDefs; +} + +ProtocolDef.prototype.addInstanceMethod = function(methodDef) { + (this.requiredInstanceMethods || (this.requiredInstanceMethods = Object.create(null)))[methodDef.name] = methodDef; +} + +ProtocolDef.prototype.addClassMethod = function(methodDef) { + (this.requiredClassMethods || (this.requiredClassMethods = Object.create(null)))[methodDef.name] = methodDef; +} + +ProtocolDef.prototype.getInstanceMethod = function(name) { + var instanceMethods = this.requiredInstanceMethods; + + if (instanceMethods) { + var method = instanceMethods[name]; + + if (method) + return method; + } + + var protocols = this.protocols; + + for (var i = 0, size = protocols.length; i < size; i++) { + var protocol = protocols[i], + method = protocol.getInstanceMethod(name); + + if (method) + return method; + } + + return null; +} + +ProtocolDef.prototype.getClassMethod = function(name) { + var classMethods = this.requiredClassMethods; + + if (classMethods) { + var method = classMethods[name]; + + if (method) + return method; + } + + var protocols = this.protocols; + + for (var i = 0, size = protocols.length; i < size; i++) { + var protocol = protocols[i], + method = protocol.getInstanceMethod(name); + + if (method) + return method; + } + + return null; +} + +// methodDef = {"types": types, "name": selector} +var MethodDef = function(name, types) +{ + this.name = name; + this.types = types; +} + var currentCompilerFlags = ""; var reservedIdentifiers = exports.acorn.makePredicate("self _cmd undefined localStorage arguments"); @@ -153,7 +367,7 @@ var wordPrefixOperators = exports.acorn.makePredicate("delete in instanceof new var isLogicalBinary = exports.acorn.makePredicate("LogicalExpression BinaryExpression"); var isInInstanceof = exports.acorn.makePredicate("in instanceof"); -var ObjJAcornCompiler = function(/*String*/ aString, /*CFURL*/ aURL, /*unsigned*/ flags, /*unsigned*/ pass, /* Dictionary */ classDefs) +var ObjJAcornCompiler = function(/*String*/ aString, /*CFURL*/ aURL, /*unsigned*/ flags, /*unsigned*/ pass, /* Dictionary */ classDefs, /* Dictionary */ protocolDefs) { this.source = aString; this.URL = new CFURL(aURL); @@ -182,6 +396,7 @@ var ObjJAcornCompiler = function(/*String*/ aString, /*CFURL*/ aURL, /*unsigned* this.dependencies = []; this.flags = flags | ObjJAcornCompiler.Flags.IncludeDebugSymbols; this.classDefs = classDefs ? classDefs : Object.create(null); + this.protocolDefs = protocolDefs ? protocolDefs : Object.create(null); this.lastPos = 0; if (currentCompilerFlags & ObjJAcornCompiler.Flags.Generate) this.generate = true; @@ -215,8 +430,8 @@ ObjJAcornCompiler.prototype.compilePass2 = function() this.pass = 2; this.jsBuffer = new StringBuffer(); this.warnings = []; + //print(this.URL + ": Compiling"); compile(this.tokens, new Scope(null ,{ compiler: this }), pass2); - for (var i = 0; i < this.warnings.length; i++) { var message = this.prettifyMessage(this.warnings[i], "WARNING"); @@ -227,6 +442,7 @@ ObjJAcornCompiler.prototype.compilePass2 = function() #endif } + //print(this.URL + ": " + this.jsBuffer.toString()); return this.jsBuffer.toString(); } @@ -271,7 +487,7 @@ ObjJAcornCompiler.prototype.getIvarForClass = function(/* String */ ivarName, /* if (ivarDef) return ivarDef; } - c = this.getClassDef(c.superClassName); + c = c.superClass; } } @@ -279,37 +495,100 @@ ObjJAcornCompiler.prototype.getClassDef = function(/* String */ aClassName) { if (!aClassName) return null; - var c = this.classDefs[aClassName]; + var c = this.classDefs[aClassName]; - if (c) return c; + if (c) return c; - if (objj_getClass) - { - var aClass = objj_getClass(aClassName); - if (aClass) - { - var ivars = class_copyIvarList(aClass), - ivarSize = ivars.length, - myIvars = Object.create(null), - superClass = aClass.super_class; + if (objj_getClass) + { + var aClass = objj_getClass(aClassName); + if (aClass) + { + var ivars = class_copyIvarList(aClass), + ivarSize = ivars.length, + myIvars = Object.create(null), + protocols = class_copyProtocolList(aClass), + protocolSize = protocols.length, + myProtocols = Object.create(null), + instanceMethodDefs = ObjJAcornCompiler.methodDefsFromMethodList(class_copyMethodList(aClass)), + classMethodDefs = ObjJAcornCompiler.methodDefsFromMethodList(class_copyMethodList(aClass.isa)), + superClass = class_getSuperclass(aClass); - for (var i = 0; i < ivarSize; i++) - { - var ivar = ivars[i]; + for (var i = 0; i < ivarSize; i++) + { + var ivar = ivars[i]; - myIvars[ivar.name] = {"type": ivar.type, "name": ivar.name}; - } - c = {"className": aClassName, "ivars": myIvars}; + myIvars[ivar.name] = {"type": ivar.type, "name": ivar.name}; + } - if (superClass) - c.superClassName = superClass.name; - this.classDefs[aClassName] = c; - return c; - } - } + for (var i = 0; i < protocolSize; i++) + { + var protocol = protocols[i], + protocolName = protocol_getName(protocol), + protocolDef = this.getProtocolDef(protocolName); - return null; -// classDef = {"className": className, "superClassName": superClassName, "ivars": Object.create(null), "methods": Object.create(null)}; + myProtocols[protocolName] = protocolDef; + } + + c = new ClassDef(true, aClassName, superClass ? this.getClassDef(superClass.name) : null, myIvars, instanceMethodDefs, classMethodDefs, myProtocols); + this.classDefs[aClassName] = c; + return c; + } + } + + return null; +} + +ObjJAcornCompiler.prototype.getProtocolDef = function(/* String */ aProtocolName) +{ + if (!aProtocolName) return null; + + var p = this.protocolDefs[aProtocolName]; + + if (p) return p; + + if (objj_getProtocol) + { + var aProtocol = objj_getProtocol(aProtocolName); + if (aProtocol) + { + var protocol = protocols[i], + protocolName = protocol_getName(protocol), + requiredInstanceMethods = protocol_copyMethodDescriptionList(protocol, true, true), + requiredInstanceMethodDefs = ObjJAcornCompiler.methodDefsFromMethodList(requiredInstanceMethods), + requiredClassMethods = protocol_copyMethodDescriptionList(protocol, true, false), + requiredClassMethodDefs = ObjJAcornCompiler.methodDefsFromMethodList(requiredClassMethods), + protocols = protocol.protocols, + inheritFromProtocols = []; + + for (var i = 0, size = protocols.length; i < size; i++) + inheritFromProtocols.push(compiler.getProtocolDef(protocols[i].name)); + + p = new ProtocolDef(protocolName, inheritFromProtocols, requiredInstanceMethodDefs, requiredClassMethodDefs); + + this.protocolDefs[aProtocolName] = p; + return c; + } + } + + return null; +// protocolDef = {"name": protocolName, "protocols": Object.create(null), "required": Object.create(null), "optional": Object.create(null)}; +} + +ObjJAcornCompiler.methodDefsFromMethodList = function(/* Array */ methodList) +{ + var methodSize = methodList.length, + myMethods = Object.create(null); + + for (var i = 0; i < methodSize; i++) + { + var method = methodList[i], + methodName = method_getName(method); + + myMethods[methodName] = new MethodDef(methodName, method.types); + } + + return myMethods; } ObjJAcornCompiler.prototype.executable = function() @@ -372,7 +651,9 @@ function createMessage(/* String */ aMessage, /* SpiderMonkey AST node */ node, function compile(node, state, visitor) { function c(node, st, override) { + //print("c: " + (override ? override + ", " : "") + node.type + ", " + exports.acorn.getLineInfo(st.compiler.source, node.start).line); visitor[override || node.type](node, st, c); + //print("cc: " + (override ? override + ", " : "") + node.type + ", " + exports.acorn.getLineInfo(st.compiler.source, node.end).line); } c(node, state); }; @@ -1300,10 +1581,12 @@ ImportStatement: function(node, st, c) { ClassDeclarationStatement: function(node, st, c) { var compiler = st.compiler, generate = compiler.generate, - classDef, saveJSBuffer = compiler.jsBuffer, className = node.classname.name, - classScope = new Scope(st); + classDef = compiler.getClassDef(className), + classScope = new Scope(st), + isInterfaceDeclaration = node.type === "InterfaceDeclarationStatement", + protocols = node.protocols; compiler.imBuffer = new StringBuffer(); compiler.cmBuffer = new StringBuffer(); @@ -1314,10 +1597,19 @@ ClassDeclarationStatement: function(node, st, c) { // First we declare the class if (node.superclassname) { - classDef = compiler.getClassDef(className); - if (classDef && classDef.ivars) // Must have ivars dictionary to be a real declaration. Without it is a "@class" declaration + // Must have methods dictionaries and ivars dictionary to be a real implementaion declaration. + // Without it is a "@class" declaration (without both ivars dictionary and method dictionaries) or + // "interface" declaration (without ivars dictionary) + // TODO: Create a ClassDef object and add this logic to it + if (classDef && classDef.ivars) + // It has a real implementation declaration already throw compiler.error_message("Duplicate class " + className, node.classname); - if (!compiler.getClassDef(node.superclassname.name)) + + if (isInterfaceDeclaration && classDef && classDef.instanceMethods && classDef.classMethods) + // It has a interface declaration already + throw compiler.error_message("Duplicate interface definition for class " + className, node.classname); + var superClassDef = compiler.getClassDef(node.superclassname.name); + if (!superClassDef) { var errorMessage = "Can't find superclass " + node.superclassname.name; for (var i = ObjJAcornCompiler.importStack.length; --i >= 0;) @@ -1325,7 +1617,7 @@ ClassDeclarationStatement: function(node, st, c) { throw compiler.error_message(errorMessage, node.superclassname); } - classDef = {"className": className, "superClassName": node.superclassname.name, "ivars": Object.create(null), "methods": Object.create(null)}; + classDef = new ClassDef(!isInterfaceDeclaration, className, superClassDef, Object.create(null)); saveJSBuffer.concat("{var the_class = objj_allocateClassPair(" + node.superclassname.name + ", \"" + className + "\"),\nmeta_class = the_class.isa;"); } @@ -1341,11 +1633,21 @@ ClassDeclarationStatement: function(node, st, c) { } else { - classDef = {"className": className, "superClassName": null, "ivars": Object.create(null), "methods": Object.create(null)}; + classDef = new ClassDef(!isInterfaceDeclaration, className, null, Object.create(null)); saveJSBuffer.concat("{var the_class = objj_allocateClassPair(Nil, \"" + className + "\"),\nmeta_class = the_class.isa;"); } + if (protocols) for (var i = 0, size = protocols.length; i < size; i++) + { + saveJSBuffer.concat("\nvar aProtocol = objj_getProtocol(\"" + protocols[i].name + "\");"); + saveJSBuffer.concat("\nif (!aProtocol) throw new SyntaxError(\"*** Could not find definition for protocol \\\"" + protocols[i].name + "\\\"\");"); + saveJSBuffer.concat("\nclass_addProtocol(the_class, aProtocol);"); + } +/* + if (isInterfaceDeclaration) + classDef.interfaceDeclaration = true; +*/ classScope.classDef = classDef; compiler.currentSuperClass = "objj_getClass(\"" + className + "\").super_class"; compiler.currentSuperMetaClass = "objj_getMetaClass(\"" + className + "\").super_class"; @@ -1359,8 +1661,12 @@ ClassDeclarationStatement: function(node, st, c) { var ivarDecl = node.ivardeclarations[i], ivarType = ivarDecl.ivartype ? ivarDecl.ivartype.name : null, ivarName = ivarDecl.id.name, + ivars = classDef.ivars, ivar = {"type": ivarType, "name": ivarName}; + if (ivars[ivarName]) + throw compiler.error_message("Instance variable '" + ivarName + "'is already declared for class " + className, ivarDecl.id); + if (firstIvarDeclaration) { firstIvarDeclaration = false; @@ -1376,7 +1682,7 @@ ClassDeclarationStatement: function(node, st, c) { if (ivarDecl.outlet) ivar.outlet = true; - classDef.ivars[ivarName] = ivar; + ivars[ivarName] = ivar; if (!classScope.ivars) classScope.ivars = Object.create(null); classScope.ivars[ivarName] = {type: "ivar", name: ivarName, node: ivarDecl.id, ivar: ivar}; @@ -1389,7 +1695,7 @@ ClassDeclarationStatement: function(node, st, c) { saveJSBuffer.concat("]);"); // If we have accessors add get and set methods for them - if (hasAccessors) + if (!isInterfaceDeclaration && hasAccessors) { var getterSetterBuffer = new StringBuffer(); @@ -1449,20 +1755,22 @@ ClassDeclarationStatement: function(node, st, c) { // We will store the classDef first after accessors are done so we don't get a duplicate class error compiler.classDefs[className] = classDef; - if (node.body.length > 0) + var bodies = node.body, + bodyLength = bodies.length; + + if (bodyLength > 0) { - if (!generate) compiler.lastPos = node.body[0].start; + if (!generate) compiler.lastPos = bodies[0].start; // And last add methods and other statements - for (var i = 0; i < node.body.length; ++i) { - var body = node.body[i]; + for (var i = 0; i < bodyLength; ++i) { + var body = bodies[i]; c(body, classScope, "Statement"); } if (!generate) saveJSBuffer.concat(compiler.source.substring(compiler.lastPos, body.end)); } - // We must make a new class object for our class definition if it's not a category - if (!node.categoryname) { + if (!isInterfaceDeclaration && !node.categoryname) { saveJSBuffer.concat("objj_registerClassPair(the_class);\n"); } @@ -1488,23 +1796,146 @@ ClassDeclarationStatement: function(node, st, c) { // Skip the "@end" if (!generate) compiler.lastPos = node.end; + + // If the class conforms to protocols check that all required methods are implemented + if (protocols) + { + // Lookup the protocolDefs for the protocols + var protocolDefs = []; + + for (var i = 0, size = protocols.length; i < size; i++) + protocolDefs.push(compiler.getProtocolDef(protocols[i].name)); + + var unimplementedMethods = classDef.listOfNotImplementedMethodsForProtocols(protocolDefs); + + if (unimplementedMethods && unimplementedMethods.length > 0) + for (var i = 0, size = unimplementedMethods.length; i < size; i++) { + var unimplementedMethod = unimplementedMethods[i], + methodDef = unimplementedMethod.methodDef, + protocolDef = unimplementedMethod.protocolDef; + + compiler.addWarning(createMessage("Method '" + methodDef.name + "' in protocol '" + protocolDef.name + "' is not implemented", node.classname, compiler.source)); + } + } +}, +ProtocolDeclarationStatement: function(node, st, c) { + var compiler = st.compiler, + generate = compiler.generate, + buffer = compiler.jsBuffer, + protocolName = node.protocolname.name, + protocolDef = compiler.getProtocolDef(protocolName), + protocols = node.protocols, + protocolScope = new Scope(st), + inheritFromProtocols = []; + + if (protocolDef) + throw compiler.error_message("Duplicate protocol " + protocolName, node.protocolName); + + compiler.imBuffer = new StringBuffer(); + compiler.cmBuffer = new StringBuffer(); + + if (!generate) buffer.concat(compiler.source.substring(compiler.lastPos, node.start)); + + buffer.concat("{var the_protocol = objc_allocateProtocol(\"" + protocolName + "\");"); + + if (protocols) for (var i = 0, size = protocols.length; i < size; i++) + { + var protocol = protocols[i], + inheritFromProtocolName = protocol.name; + inheritProtocolDef = compiler.getProtocolDef(inheritFromProtocolName); + + if (!inheritProtocolDef) + throw compiler.error_message("Can't find protocol " + inheritFromProtocolName, protocol); + + buffer.concat("\nvar aProtocol = objj_getProtocol(\"" + inheritFromProtocolName + "\");"); + buffer.concat("\nif (!aProtocol) throw new SyntaxError(\"*** Could not find definition for protocol \\\"" + protocolName + "\\\"\");"); + buffer.concat("\nprotocol_addProtocol(the_protocol, aProtocol);"); + inheritFromProtocols.push(inheritProtocolDef); + } + + protocolDef = new ProtocolDef(protocolName, inheritFromProtocols); + compiler.protocolDefs[protocolName] = protocolDef; + protocolScope.protocolDef = protocolDef; + + var someRequired = node.required, + requiredLength = someRequired.length; + + if (requiredLength > 0) + { + // We only add the required methods + for (var i = 0; i < requiredLength; ++i) { + var required = someRequired[i]; + if (!generate) compiler.lastPos = required.start; + c(required, protocolScope, "Statement"); + } + if (!generate) buffer.concat(compiler.source.substring(compiler.lastPos, required.end)); + } + + buffer.concat("\nobjc_registerProtocol(the_protocol);\n"); + + // Add instance methods + if (compiler.imBuffer.isEmpty()) + { + buffer.concat("protocol_addMethodDescriptions(the_protocol, ["); + buffer.atoms.push.apply(buffer.atoms, compiler.imBuffer.atoms); // FIXME: Move this append to StringBuffer + buffer.concat("], true, false);\n"); + } + + // Add class methods + if (compiler.cmBuffer.isEmpty()) + { + buffer.concat("protocol_addMethodDescriptions(the_protocol, ["); + buffer.atoms.push.apply(buffer.atoms, compiler.cmBuffer.atoms); // FIXME: Move this append to StringBuffer + buffer.concat("], true, true);\n"); + } + + buffer.concat("}"); + + compiler.jsBuffer = buffer; + + // Skip the "@end" + if (!generate) compiler.lastPos = node.end; }, MethodDeclarationStatement: function(node, st, c) { var compiler = st.compiler, generate = compiler.generate, saveJSBuffer = compiler.jsBuffer, methodScope = new Scope(st), + isInstanceMethodType = node.methodtype === '-'; selectors = node.selectors, arguments = node.arguments, - types = [node.returntype ? node.returntype.name : "id"], + returnType = node.returntype, + types = [returnType ? returnType.name : "id"], + returnTypeProtocols = returnType ? returnType.protocols : null; selector = selectors[0].name; // There is always at least one selector + if (returnTypeProtocols) for (var i = 0, size = returnTypeProtocols.length; i < size; i++) { + var returnTypeProtocol = returnTypeProtocols[i]; + if (!compiler.getProtocolDef(returnTypeProtocol.name)) { + compiler.addWarning(createMessage("Cannot find protocol declaration for '" + returnTypeProtocol.name + "'", returnTypeProtocol, compiler.source)); + } + } + if (!generate) saveJSBuffer.concat(compiler.source.substring(compiler.lastPos, node.start)); - compiler.jsBuffer = node.methodtype === '-' ? compiler.imBuffer : compiler.cmBuffer; + compiler.jsBuffer = isInstanceMethodType ? compiler.imBuffer : compiler.cmBuffer; // Put together the selector. Maybe this should be done in the parser... for (var i = 0; i < arguments.length; i++) { + var argument = arguments[i], + argumentType = argument.type, + argumentTypeName = argumentType ? argumentType.name : "id", + argumentProtocols = argumentType ? argumentType.protocols : null; + + types.push(argumentType ? argumentType.name : "id"); + + if (argumentProtocols) for (var i = 0, size = argumentProtocols.length; i < size; i++) { + var argumentProtocol = argumentProtocols[i]; + if (!compiler.getProtocolDef(argumentProtocol.name)) { + compiler.addWarning(createMessage("Cannot find protocol declaration for '" + argumentProtocol.name + "'", argumentProtocol, compiler.source)); + } + } + if (i === 0) selector += ":"; else @@ -1513,45 +1944,111 @@ MethodDeclarationStatement: function(node, st, c) { if (compiler.jsBuffer.isEmpty()) // Add comma separator if this is not first method in this buffer compiler.jsBuffer.concat(", "); + compiler.jsBuffer.concat("new objj_method(sel_getUid(\""); compiler.jsBuffer.concat(selector); - compiler.jsBuffer.concat("\"), function"); + compiler.jsBuffer.concat("\"), "); -// this.currentSelector = selector; + if (node.body) { + compiler.jsBuffer.concat("function"); - if (compiler.flags & ObjJAcornCompiler.Flags.IncludeDebugSymbols) - { - compiler.jsBuffer.concat(" $" + st.currentClassName() + "__" + selector.replace(/:/g, "_")); + if (compiler.flags & ObjJAcornCompiler.Flags.IncludeDebugSymbols) + { + compiler.jsBuffer.concat(" $" + st.currentClassName() + "__" + selector.replace(/:/g, "_")); + } + + compiler.jsBuffer.concat("(self, _cmd"); + + methodScope.methodType = node.methodtype; + if (arguments) for (var i = 0; i < arguments.length; i++) + { + var argument = arguments[i], + argumentName = argument.identifier.name; + + compiler.jsBuffer.concat(", "); + compiler.jsBuffer.concat(argumentName); + methodScope.vars[argumentName] = {type: "method argument", node: argument}; + } + + compiler.jsBuffer.concat(")\n"); + + if (!generate) compiler.lastPos = node.startOfBody; + indentation += indentStep; + c(node.body, methodScope, "Statement"); + indentation = indentation.substring(indentationSpaces); + if (!generate) compiler.jsBuffer.concat(compiler.source.substring(compiler.lastPos, node.body.end)); + + compiler.jsBuffer.concat("\n"); + } else { // It is a interface or protocol declatartion and we don't have a method implementation + compiler.jsBuffer.concat("Nil\n"); } - compiler.jsBuffer.concat("(self, _cmd"); - - methodScope.methodType = node.methodtype; - if (arguments) for (var i = 0; i < arguments.length; i++) - { - var argument = arguments[i], - argumentName = argument.identifier.name; - - compiler.jsBuffer.concat(", "); - compiler.jsBuffer.concat(argumentName); - types.push(argument.type ? argument.type.name : null); - methodScope.vars[argumentName] = {type: "method argument", node: argument}; - } - - compiler.jsBuffer.concat(")\n"); - - if (!generate) compiler.lastPos = node.startOfBody; - indentation += indentStep; - c(node.body, methodScope, "Statement"); - indentation = indentation.substring(indentationSpaces); - if (!generate) compiler.jsBuffer.concat(compiler.source.substring(compiler.lastPos, node.body.end)); - - compiler.jsBuffer.concat("\n"); if (compiler.flags & ObjJAcornCompiler.Flags.IncludeDebugSymbols) compiler.jsBuffer.concat(","+JSON.stringify(types)); + compiler.jsBuffer.concat(")"); compiler.jsBuffer = saveJSBuffer; + if (!generate) compiler.lastPos = node.end; + + // Add the method to the class or protocol definition + var def = st.classDef, + alreadyDeclared; + + // But first, if it is a class definition check if it is declared in superclass or interface declaration + if (def) + alreadyDeclared = isInstanceMethodType ? def.getInstanceMethod(selector) : def.getClassMethod(selector); + else + def = st.protocolDef; + + if (!def) + throw "InternalError: MethodDeclaration without ClassDeclaration or ProtocolDeclaration at line: " + exports.acorn.getLineInfo(compiler.source, node.start).line; + + // Create warnings if types does not corresponds to method declaration in superclass or interface declarations + // If we don't find the method in superclass or interface declarations above or if it is a protocol + // declaration, try to find it in any of the conforming protocols + if (!alreadyDeclared) { + var protocols = def.protocols; + + if (protocols) for (var i = 0, size = protocols.length; i < size; i++) { + var protocol = protocols[i], + alreadyDeclared = isInstanceMethodType ? protocol.getInstanceMethod(selector) : protocol.getClassMethod(selector); + + if (alreadyDeclared) + break; + } + } + + if (alreadyDeclared) { + var declaredTypes = alreadyDeclared.types; + + if (declaredTypes) { + var typeSize = declaredTypes.length; + if (typeSize > 0) { + // First type is return type + var returnType = declaredTypes[0]; + + if (returnType !== types[0]) + compiler.addWarning(createMessage("Conflicting return type in implementation of '" + selector + "': '" + returnType + "' vs '" + types[0] + "'", node.returntype || node, compiler.source)); + + // Check the parameter types. The size of the two type arrays should be the same + for (var i = 1; i < typeSize; i++) { + var parameterType = declaredTypes[i]; + + if (parameterType !== types[i]) + compiler.addWarning(createMessage("Conflicting parameter types in implementation of '" + selector + "': '" + parameterType + "' vs '" + types[i] + "'", node.arguments[i - 1].type || node.arguments[i - 1].identifier, compiler.source)); + } + } + } + } + + // Now we add it + var methodDef = new MethodDef(selector, types); + + if (isInstanceMethodType) + def.addInstanceMethod(methodDef); + else + def.addClassMethod(methodDef); }, MessageSendExpression: function(node, st, c) { var compiler = st.compiler, @@ -1636,6 +2133,19 @@ SelectorLiteralExpression: function(node, st, c) { buffer.concat("\")"); if (!generate) compiler.lastPos = node.end; }, +ProtocolLiteralExpression: function(node, st, c) { + var compiler = st.compiler, + buffer = compiler.jsBuffer, + generate = compiler.generate; + if (!generate) { + buffer.concat(compiler.source.substring(compiler.lastPos, node.start)); + buffer.concat(" "); // Add an extra space if it looks something like this: "return(@protocol(a))". No space between return and expression. + } + buffer.concat("objj_getProtocol(\""); + buffer.concat(node.id.name); + buffer.concat("\")"); + if (!generate) compiler.lastPos = node.end; +}, Reference: function(node, st, c) { var compiler = st.compiler, buffer = compiler.jsBuffer, @@ -1677,7 +2187,7 @@ ClassStatement: function(node, st, c) { } var className = node.id.name; if (!compiler.getClassDef(className)) { - classDef = {"className": className}; + classDef = new ClassDef(false, className); compiler.classDefs[className] = classDef; } st.vars[node.id.name] = {type: "class", node: node.id}; diff --git a/Objective-J/Runtime.js b/Objective-J/Runtime.js index e414f8502..b9de057f6 100644 --- a/Objective-J/Runtime.js +++ b/Objective-J/Runtime.js @@ -47,7 +47,7 @@ GLOBAL(objj_ivar) = function(/*String*/ aName, /*String*/ aType) this.type = aType; } -GLOBAL(objj_method) = function(/*String*/ aName, /*IMP*/ anImplementation, /*String*/ types) +GLOBAL(objj_method) = function(/*String*/ aName, /*IMP*/ anImplementation, /*Array*/ types) { this.name = aName; this.method_imp = anImplementation; @@ -74,6 +74,8 @@ GLOBAL(objj_class) = function(displayName) 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 @@ -85,6 +87,13 @@ GLOBAL(objj_class) = function(displayName) 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; @@ -288,6 +297,139 @@ GLOBAL(class_replaceMethod) = function(/*Class*/ aClass, /*SEL*/ aSelector, /*IM 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 = { }; + +GLOBAL(objc_allocateProtocol) = function(/*String*/ aName) +{ + var protocol = new objj_protocol(aName); + + return protocol; +} + +GLOBAL(objc_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.name] = method; + } +} + +GLOBAL(protocol_copyMethodDescriptionList) = function(/*Protocol*/ proto, /*BOOL*/ isRequiredMethod, /*BOOL*/ isInstanceMethod) +{ + return isRequiredMethod ? (isInstanceMethod ? proto.instance_methods : proto.class_methods).slice(0) : []; +} + +GLOBAL(protocol_addProtocol) = function(/*Protocol*/ proto, /*Protocol*/ addition) +{ + if (!proto || !addition) return; + + (proto.protocol_list || (proto.protocol_list = [])).push(addition); +} + var _class_initialize = function(/*Class*/ aClass) { var meta = GETMETA(aClass); @@ -567,6 +709,13 @@ GLOBAL(objj_getMetaClass) = function(/*String*/ aName) return GETMETA(theClass); } +// Working with Protocol + +GLOBAL(objj_getProtocol) = function(/*String*/ aName) +{ + return REGISTERED_PROTOCOLS[aName]; +} + // Working with Instance Variables GLOBAL(ivar_getName) = function(anIvar) diff --git a/Objective-J/acorn.js b/Objective-J/acorn.js index 8eb0d89f1..6bf3a54a8 100644 --- a/Objective-J/acorn.js +++ b/Objective-J/acorn.js @@ -373,6 +373,8 @@ if (typeof exports != "undefined" && !exports.acorn) { var _action = {keyword: "action"}, _selector = {keyword: "selector"}, _class = {keyword: "class"}, _global = {keyword: "global"}; var _dictionaryLiteral = {keyword: "{"}, _arrayLiteral = {keyword: "["}; var _ref = {keyword: "ref"}, _deref = {keyword: "deref"}; + var _protocol = {keyword: "protocol"}, _optional = {keyword: "optional"}, _required = {keyword: "required"}; + var _interface = {keyword: "interface"}; // Objective-J keywords @@ -418,7 +420,8 @@ if (typeof exports != "undefined" && !exports.acorn) { var objJAtKeywordTypes = {"implementation": _implementation, "outlet": _outlet, "accessors": _accessors, "end": _end, "import": _import, "action": _action, "selector": _selector, "class": _class, "global": _global, - "ref": _ref, "deref": _deref}; + "ref": _ref, "deref": _deref, "protocol": _protocol, "optional": _optional, "required": _required, + "interface": _interface}; // Map Preprocessor keyword names to token types. @@ -2103,7 +2106,7 @@ var preIfLevel = 0; return finishNode(node, "EmptyStatement"); // This is a Objective-J statement - case _implementation: + case _interface: if (options.objj) { next(); node.classname = parseIdent(true); @@ -2122,42 +2125,147 @@ var preIfLevel = 0; node.endOfIvars = tokStart; } node.body = []; + while(!eat(_end)) { + if (tokType === _eof) raise(tokPos, "Expected '@end' after '@interface'"); + node.body.push(parseClassElement()); + } + return finishNode(node, "InterfaceDeclarationStatement"); + } + break; + + // This is a Objective-J statement + case _implementation: + if (options.objj) { + next(); + node.classname = parseIdent(true); + if (eat(_colon)) + node.superclassname = parseIdent(true); + else if (eat(_parenL)) { + node.categoryname = parseIdent(true); + expect(_parenR, "Expected closing ')' after category name"); + } + if (tokVal === '<') { + next(); + var protocols = [], + first = true; + node.protocols = protocols; + while (tokVal !== '>') { + if (!first) + expect(_comma, "Expected ',' between protocol names"); + else first = false; + protocols.push(parseIdent(true)); + } + next(); + } + if (tokVal === '<') { + next(); + var protocols = [], + first = true; + node.protocols = protocols; + while (tokVal !== '>') { + if (!first) + expect(_comma, "Expected ',' between protocol names"); + else first = false; + protocols.push(parseIdent(true)); + } + next(); + } + if (eat(_braceL)) { + node.ivardeclarations = []; + for (;;) { + if (eat(_braceR)) break; + parseIvarDeclaration(node); + } + node.endOfIvars = tokStart; + } + node.body = []; while(!eat(_end)) { if (tokType === _eof) raise(tokPos, "Expected '@end' after '@implementation'"); node.body.push(parseClassElement()); } + return finishNode(node, "ClassDeclarationStatement"); } - return finishNode(node, "ClassDeclarationStatement"); + break; + + // This is a Objective-J statement + case _protocol: + // If next token is a left parenthesis it is a ProtocolLiternal expression so bail out + if (options.objj && input.charCodeAt(tokPos) !== 40) { // '(' + next(); + node.protocolname = parseIdent(true); + if (tokVal === '<') { + next(); + var protocols = [], + first = true; + node.protocols = protocols; + while (tokVal !== '>') { + if (!first) + expect(_comma, "Expected ',' between protocol names"); + else first = false; + protocols.push(parseIdent(true)); + } + next(); + } + while(!eat(_end)) { + if (tokType === _eof) raise(tokPos, "Expected '@end' after '@protocol'"); + if (eat(_optional)) { + while(!eat(_required && tokType !== _end)) { + (node.optional || (node.optional = [])).push(parseProtocolClassElement()); + } + } else { + (node.required || (node.required = [])).push(parseProtocolClassElement()); + } + } + return finishNode(node, "ProtocolDeclarationStatement"); + } + break; // This is a Objective-J statement case _import: - next(); - if (tokType === _string) - node.localfilepath = true; - else if (tokType ===_filename) - node.localfilepath = false; - else - unexpected(); + if (options.objj) { + next(); + if (tokType === _string) + node.localfilepath = true; + else if (tokType ===_filename) + node.localfilepath = false; + else + unexpected(); - node.filename = parseStringNumRegExpLiteral(); - return finishNode(node, "ImportStatement"); + node.filename = parseStringNumRegExpLiteral(); + return finishNode(node, "ImportStatement"); + } + break; // This is a Objective-J statement case _preprocess: - next(); - return finishNode(node, "PreprocessStatement"); + if (options.objj) { + next(); + return finishNode(node, "PreprocessStatement"); + } + break; // This is a Objective-J statement case _class: - next(); - node.id = parseIdent(false); - return finishNode(node, "ClassStatement"); + if (options.objj) { + next(); + node.id = parseIdent(false); + return finishNode(node, "ClassStatement"); + } + break; // This is a Objective-J statement case _global: - next(); - node.id = parseIdent(false); - return finishNode(node, "GlobalStatement"); + if (options.objj) { + next(); + node.id = parseIdent(false); + return finishNode(node, "GlobalStatement"); + } + break; + + } + + // The indentation is one step to the right here to make sure it + // is the same as in the original acorn parser. Easier merge // If the statement does not start with a statement keyword or a // brace, it's an ExpressionStatement or LabeledStatement. We @@ -2165,7 +2273,6 @@ var preIfLevel = 0; // next token is a colon and the expression was a simple // Identifier node, we switch to interpreting it as a label. - default: var maybeName = tokVal, expr = parseExpression(); if (starttype === _name && expr.type === "Identifier" && eat(_colon)) { for (var i = 0; i < labels.length; ++i) @@ -2181,18 +2288,8 @@ var preIfLevel = 0; semicolon(); return finishNode(node, "ExpressionStatement"); } - } } - // CompoundIvarDeclaration = - // IvarType _ IvarDeclaration (_ "," _ IvarDeclaration)* EOS - - // IvarDeclaration = - // Identifier _ Accessors? - - // Accessors = - // "@accessors" ("(" (AccessorsConfiguration (_ "," _ AccessorsConfiguration)*)? ")")? - function parseIvarDeclaration(node) { var outlet; if (eat(_outlet)) @@ -2252,49 +2349,53 @@ var preIfLevel = 0; semicolon(); } - function parseClassElement() { - var methodType = tokVal, - element = startNode(); - if (eat(_plusmin)) { - element.methodtype = methodType; - // If we find a '(' we have a return type to parse + function parseMethodDeclaration(node) { + node.methodtype = tokVal; + expect(_plusmin, "Method declaration must start with '+' or '-'"); + // If we find a '(' we have a return type to parse + if (eat(_parenL)) { + if (eat(_action)) + node.action = true; + if (!eat(_parenR)) { + node.returntype = parseObjectiveJType(); + expect(_parenR, "Expected closing ')' after method return type"); + } + } + // Now we parse the selector + var first = true, + selectors = [], + args = []; + node.selectors = selectors; + node.arguments = args; + for (;;) { + if (tokType !== _colon) { + selectors.push(parseIdent(true)); + if (first && tokType !== _colon) break; + } else + selectors.push(null); + expect(_colon, "Expected ':' in selector"); + var argument = {}; + args.push(argument); if (eat(_parenL)) { - if (eat(_action)) - element.action = true; - if (!eat(_parenR)) { - element.returntype = parseObjectiveJType(); - expect(_parenR, "Expected closing ')' after method return type"); - } + argument.type = parseObjectiveJType(); + expect(_parenR, "Expected closing ')' after method argument type"); } - // Now we parse the selector - var first = true, - selectors = [], - args = []; - element.selectors = selectors; - element.arguments = args; - for (;;) { - if (tokType !== _colon) { - selectors.push(parseIdent(true)); - if (first && tokType !== _colon) break; - } else - selectors.push(null); - expect(_colon, "Expected ':' in selector"); - var argument = {}; - args.push(argument); - if (eat(_parenL)) { - argument.type = parseObjectiveJType(); - expect(_parenR, "Expected closing ')' after method argument type"); - } - argument.identifier = parseIdent(false); - if (tokType === _braceL || eat(_semi)) break; - if (eat(_comma)) { - expect(_dotdotdot, "Expected '...' after ',' in method declaration"); - element.parameters = true; - break; - } - first = false; + argument.identifier = parseIdent(false); + if (tokType === _braceL || tokType === _semi) break; + if (eat(_comma)) { + expect(_dotdotdot, "Expected '...' after ',' in method declaration"); + node.parameters = true; + break; } + first = false; + } + } + function parseClassElement() { + var element = startNode(); + if (tokVal === '+' || tokVal === '-') { + parseMethodDeclaration(element); + eat(_semi); element.startOfBody = lastEnd; // Start a new scope with regard to labels and the `inFunction` // flag (restore them to their old value afterwards). @@ -2307,6 +2408,14 @@ var preIfLevel = 0; return parseStatement(); } + function parseProtocolClassElement() { + var element = startNode(); + parseMethodDeclaration(element); + + semicolon(); + return finishNode(element, "MethodDeclarationStatement"); + } + // Used for constructs like `switch` and `if` that insist on // parentheses around their expression. @@ -2622,6 +2731,14 @@ var preIfLevel = 0; expect(_parenR, "Expected closing ')' after selector"); return finishNode(node, "SelectorLiteralExpression"); + case _protocol: + var node = startNode(); + next(); + expect(_parenL, "Expected '(' after '@protocol'"); + node.id = parseIdent(true); + expect(_parenR, "Expected closing ')' after protocol name"); + return finishNode(node, "ProtocolLiteralExpression"); + case _ref: var node = startNode(); next(); @@ -2878,12 +2995,21 @@ var preIfLevel = 0; function parseObjectiveJType() { var node = startNode(); if (tokType === _name) { - node.name = tokVal; + var type = tokVal; + node.name = type; next(); - if (tokVal === '<') { - next(); - node.protocol = parseIdent(true); - if (tokVal !== '>') unexpected(); + if (type === "id" && tokVal === '<') { + var first = true, + protocols = []; + node.protocols = protocols; + do { + next(); + if (first) + first = false; + else + eat(_comma); + protocols.push(parseIdent(true)); + } while (tokVal !== '>'); next(); } } else { diff --git a/Objective-J/acornwalk.js b/Objective-J/acornwalk.js index b1fa478d4..87753e3f4 100644 --- a/Objective-J/acornwalk.js +++ b/Objective-J/acornwalk.js @@ -208,14 +208,23 @@ if (!exports.acorn) { exports.IvarDeclaration = ignore; - exports.MethodDeclarationStatement = ignore; - exports.PreprocessStatement = ignore; exports.ClassStatement = ignore; exports.GlobalStatement = ignore; + exports.ProtocolDeclarationStatement = function(node, st, c) { + if (node.required) for (var i = 0; i < node.required.length; ++i) { + c(node.required[i], st, "Statement"); + } + if (node.optional) for (var i = 0; i < node.optional.length; ++i) { + c(node.optional[i], st, "Statement"); + } + } + exports.MethodDeclarationStatement = function(node, st, c) { - c(node.body, st, "Statement"); + var body = node.body; + if (body) + c(body, st, "Statement"); } exports.MessageSendExpression = function(node, st, c) { @@ -227,6 +236,7 @@ if (!exports.acorn) { } exports.SelectorLiteralExpression = ignore; + exports.ProtocolLiteralExpression = ignore; exports.Reference = function(node, st, c) { c(node.element, st, "Identifier"); diff --git a/Tests/Objective-J/Preprocessor/BehaviorTests/ProtocolTest.j b/Tests/Objective-J/Preprocessor/BehaviorTests/ProtocolTest.j new file mode 100644 index 000000000..36a1a078d --- /dev/null +++ b/Tests/Objective-J/Preprocessor/BehaviorTests/ProtocolTest.j @@ -0,0 +1,72 @@ +@import + +@protocol MyProtocol + +- (int)myFunction:(int)aValue; + +@end + +@protocol MyProtocol2 + +- (int)myFunction2:(int)aValue; + +@end + +@protocol MyProtocol3 + +- (int)myFunction3:(int)aValue; + +@end + +@implementation MyClass : CPObject + +- (int)myOtherFunction:(int)aValue +{ + return aValue * 2; +} + +- (int)myFunction:(int)aValue +{ + return aValue * 2; +} + +@end + +@implementation MyClass2 : CPObject + +- (int)myOtherFunction:(int)aValue +{ + return aValue * 2; +} + +- (int)myFunction:(int)aValue +{ + return aValue * 2; +} + +- (int)myFunction2:(int)aValue +{ + return aValue * 2; +} + +- (int)myFunction3:(int)aValue +{ + return aValue * 2; +} + +@end + + +@implementation ProtocolTest : OJTestCase + +- (void)testConformsToProtocol +{ + [self assert:true equals:[[[MyClass alloc] init] conformsToProtocol:@protocol(MyProtocol)]]; + [self assert:false equals:[[[MyClass alloc] init] conformsToProtocol:@protocol(MyProtocol2)]]; + [self assert:false equals:[[[MyClass alloc] init] conformsToProtocol:@protocol(xxxxxx)]]; + [self assert:true equals:[[[MyClass2 alloc] init] conformsToProtocol:@protocol(MyProtocol)]]; + [self assert:true equals:[[[MyClass2 alloc] init] conformsToProtocol:@protocol(MyProtocol2)]]; + [self assert:true equals:[[[MyClass2 alloc] init] conformsToProtocol:@protocol(MyProtocol3)]]; +} + +@end