Objective-C FFI: intro the FFI with Objective-C
authorAlexis Laferrière <alexis.laf@xymus.net>
Wed, 27 Aug 2014 22:56:32 +0000 (18:56 -0400)
committerAlexis Laferrière <alexis.laf@xymus.net>
Wed, 10 Dec 2014 00:37:42 +0000 (19:37 -0500)
Signed-off-by: Alexis Laferrière <alexis.laf@xymus.net>

src/ffi/ffi.nit
src/ffi/objc.nit [new file with mode: 0644]

index a2dda26..019d201 100644 (file)
@@ -32,6 +32,7 @@ import c
 import cpp
 import java
 import extra_java_files
+import objc
 
 redef class MModule
        # Does this module uses the FFI?
diff --git a/src/ffi/objc.nit b/src/ffi/objc.nit
new file mode 100644 (file)
index 0000000..9ab6f7f
--- /dev/null
@@ -0,0 +1,220 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Copyright 2014 Alexis Laferrière <alexis.laf@xymus.net>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# FFI support for Objective-C
+#
+# Compiles all Objective-C code with clang. The user module must define
+# the framework used.
+#
+# This module is heavily based on the C++ FFI.
+module objc
+
+import extern_classes
+import c
+
+redef class FFILanguageAssignationPhase
+       # The Objective-C language visitor
+       var objc_language: FFILanguage = new ObjCLanguage(self)
+end
+
+redef class MModule
+       private var objc_file: nullable ObjCCompilationUnit = null
+end
+
+# The Objective-C langugage visitor
+class ObjCLanguage
+       super FFILanguage
+
+       redef fun identify_language(n) do return n.is_objc
+
+       redef fun compile_module_block(block, ecc, mmodule)
+       do
+               if mmodule.objc_file == null then mmodule.objc_file = new ObjCCompilationUnit
+
+               if block.is_objc_header then
+                       mmodule.objc_file.header_custom.add block.location.as_line_pragma
+                       mmodule.objc_file.header_custom.add block.code
+               else if block.is_objc_body then
+                       mmodule.objc_file.body_custom.add block.location.as_line_pragma
+                       mmodule.objc_file.body_custom.add block.code
+               end
+       end
+
+       redef fun compile_extern_method(block, m, ecc, mmodule)
+       do
+               if mmodule.objc_file == null then mmodule.objc_file = new ObjCCompilationUnit
+
+               var mpropdef = m.mpropdef
+               var recv_mtype = mpropdef.mclassdef.bound_mtype
+               var csignature = mpropdef.mproperty.build_csignature(
+                       recv_mtype, mmodule, "___impl", long_signature, from_objc_call_context)
+
+               var fc = new CFunction(csignature)
+               fc.decls.add block.location.as_line_pragma
+               fc.exprs.add block.code
+               mmodule.objc_file.add_exported_function fc
+       end
+
+       redef fun compile_extern_class(block, m, ecc, mmodule) do end
+
+       redef fun get_ftype(block, m) do return new ForeignObjCType(block.code)
+
+       redef fun compile_to_files(mmodule, compdir)
+       do
+               var objc_file = mmodule.objc_file
+               assert objc_file != null
+
+               # write .m and _m.h file
+               mmodule.objc_file.header_c_types.add """
+       #include "{{{mmodule.cname}}}._ffi.h"
+"""
+
+               var file = objc_file.write_to_files(mmodule, compdir)
+
+               # add compilation to makefile
+               mmodule.ffi_files.add file
+       end
+
+       redef fun compile_callback(callback, mmodule, mainmodule, ecc)
+       do
+               callback.compile_callback_to_objc(mmodule, mainmodule)
+       end
+end
+
+redef class AExternCodeBlock
+       # Is this Objective-C code?
+       fun is_objc : Bool do return language_name != null and
+               (language_name_lowered == "objc" or language_name_lowered.has_prefix("objc "))
+
+       # Is this Objective-C code for the body file?
+       fun is_objc_body : Bool do return language_name != null and
+               (language_name_lowered == "objc" or language_name_lowered == "objc body")
+
+       # Is this Objective-C code for the header file?
+       fun is_objc_header : Bool do return language_name != null and
+               (language_name_lowered == "objc header")
+end
+
+private class ObjCCompilationUnit
+       super CCompilationUnit
+
+       # Write this compilation unit to Objective-C source files
+       fun write_to_files(mmodule: MModule, compdir: String): ExternObjCFile
+       do
+               var base_name = "{mmodule.cname}._ffi"
+
+               var h_file = "{base_name}_m.h"
+               var guard = "{mmodule.cname.to_s.to_upper}_NIT_OBJC_H"
+               write_header_to_file(mmodule, compdir/h_file, new Array[String], guard)
+
+               var c_file = "{base_name}.m"
+               write_body_to_file(mmodule, compdir/c_file, ["\"{h_file}\""])
+
+               files.add compdir/c_file
+
+               mmodule.c_linker_options = "{mmodule.c_linker_options} -lobjc"
+
+               return new ExternObjCFile(compdir/c_file, mmodule)
+       end
+end
+
+# A Objective-C file
+class ExternObjCFile
+       super ExternFile
+
+       # Associated `MModule`
+       var mmodule: MModule
+
+       redef fun makefile_rule_name do return "{filename.basename(".m")}_m.o"
+       redef fun makefile_rule_content do
+               return "clang $(CFLAGS) -c {filename.basename("")} -o {makefile_rule_name}"
+       end
+       redef fun compiles_to_o_file do return true
+end
+
+# An Objective-C type
+class ForeignObjCType
+       super ForeignType
+
+       # Type name
+       var objc_type: String
+end
+
+redef class NitniCallback
+       # Compile this callback to be callable from Objective-C
+       fun compile_callback_to_objc(mmodule: MModule, mainmodule: MModule) do end
+end
+
+redef class MExplicitCall
+       redef fun compile_callback_to_objc(mmodule, mainmodule)
+       do
+               var mproperty = mproperty
+               assert mproperty isa MMethod
+
+               var objc_signature = mproperty.build_csignature(recv_mtype, mainmodule, null, short_signature, from_objc_call_context)
+               var ccall = mproperty.build_ccall(recv_mtype, mainmodule, null, long_signature, from_objc_call_context, null)
+               var fc = new CFunction(objc_signature)
+               fc.exprs.add ccall
+               mmodule.objc_file.add_local_function fc
+       end
+end
+
+# Calls withing Objective-C code
+private fun objc_call_context: ObjCCallContext do return once new ObjCCallContext
+
+# Calls from C to Objective-C
+private fun to_objc_call_context: ToObjCCallContext do return once new ToObjCCallContext
+
+# Calls from Objective-C to C
+private fun from_objc_call_context: FromObjCCallContext do return once new FromObjCCallContext
+
+private class ObjCCallContext
+       super CallContext
+
+       redef fun name_mtype(mtype)
+       do
+               if mtype isa MClassType then
+                       var ftype = mtype.mclass.ftype
+                       if ftype isa ForeignObjCType then
+                               return ftype.objc_type
+                       end
+               end
+
+               return mtype.cname
+       end
+end
+
+private class ToObjCCallContext
+       super ObjCCallContext
+
+       redef fun cast_to(mtype, name)
+       do
+               if mtype isa MClassType and mtype.mclass.ftype isa ForeignObjCType then
+                       return "(void*)({name})"
+               else return name
+       end
+end
+
+private class FromObjCCallContext
+       super ObjCCallContext
+
+       redef fun cast_from(mtype, name)
+       do
+               if mtype isa MClassType and mtype.mclass.ftype isa ForeignObjCType then
+                       return "({name_mtype(mtype)})({name})"
+               else return name
+       end
+end