Okay, so our goal here is to add support for Objective-C from Nimrod. We’d like to be able to allocate and use Objective-C objects. So the way that Nimrod works, at least the parts we are interested in, is that first it does some lexer/parser magic then creates an Abstract Syntax Tree, then from that tree it generates C code, then it runs the C code through a C compiler to generate a binary.
There are a couple ways we could interface with Objective-C (objc). We could just ignore objc syntax and interface directly with the C interface to the runtime. We could be doing things like calling the C objc_msgSend function ourselves. Or we could find a way to inject objc syntax into our generated C source files and instead compile with an objc compiler. We’ll opt for the latter because in the future it would be neat to be able to define objc classes in Nimrod and it will be easier to debug if we are using the language directly instead of just tons of C function calls into the runtime.
The first thing we’ll want to do is change from compiling with a C compiler to an objc compiler. Since objc is a super-set of C that should be easy to change and still be able to produce a valid binary. Luckily the Nimrod compiler has a flag that allows us to do this easily, namely “nimrod objc <filename>” instead of “nimrod c <filename>”. This makes the resulting code file be named with the “.m” suffix and be compiled with an objc compiler.
Okay, now we are exporting objc code, now lets try to inject some objc code into the resulting .m file. Here again Nimrod provides us with a tool to do that with the emit pragma. {.emit: “<code>”.} sends code verbatim to the resulting file. The authors of Nimrod (Araq) put it in the langauge for exactly this purpose, interfacing with other C’ish languages.
One thing to note about Nimrod is that while it allows macros on the AST it doesn’t allow us to use reader macros like common-lisp does. So we are stuck with the guidelines of the Nimrod language. It will be impossible to make “[NSDictionary alloc]” make sense in Nimrod because it would think that is an array and would be looking for the comma between NSDictionary and alloc. Also since the brackets already mean something we’ll need something special to distinguish objc message calls from arrays (eg [1,2,3,4,5]). I decided to use the syntax “o[NSDictionary, alloc]” to mean “[NSDictionary alloc]” Because of the prefix ‘o’ literal we can hunt out the difference between an array and an objc message send.
What I ended up doing was creating a Nimrod method that works on a statement, which is one or more expressions. That way it can crawl its way down the AST and perform manipulations to convert arrays prefixed with ‘o’ into emit pragmas with the corresponding objc code.
Here is a link to the code as it stands now: callobjc
Notes on understanding the code:
- Recursive expansion of objc message sends don’t exist yet. Check out head it may be done by now. That’s why you see messages alloc and init on separate lines instead of idiomatic objc on the same line.
- {.compileTime.} pragma allows functions to be usable by macros
- macros operate on PNimrodNode which can be interchangeable with stmt and expr types. I don’t believe there are any static checks to verify that something is in fact a stmt vs expr.
- the combo of getAST(<some template call>) is shortcut to avoid building an AST by hand
- literals that prefix [] operators become children of the bracket operation. I found this out by using the lispRepr proc which is indispensable when developing macros in Nimrod.