io
guide | reference | embedding

by Mac Reiter

## Introduction

First, let me say that I am learning this as I write it. As such, it may contain horrible mistakes in proper use of Io. All I can say is "it worked for me". Once you have gotten over the initial hump and have something working, I would strongly recommend poking around in the various sources (I got started by looking at IoDirectory in the IoServer package, but you may find others that are easier). Draw your own conclusions, and then use the mailing list to refine what you're doing. Having said that, here is at least one way to get something working inside an application of your own.

## Step 0 : Getting Io into your system

The programming guide has a section on embedding that contains working and well-structured code for embedding Io into your host program. It has some issues and may not compile precisely as is, but it is pretty obvious how to fix it -- the problems are simply restructuring issues meaning that you may have to replace 'state' with 'self->state'. This is fairly clear, since the only 'state' around is the one that is inside Controller, and the only Controller that is made is held by a pointer called self.

However, I assume that you are here because those instructions jumped a little too far ahead. Here at step 0, we will attempt to create the smallest possible chunk of code to get an IoVM functioning. We will also give some guidelines for getting IoVM built into your program. IoServer and IoDesktop are more involved, and you should probably get comfortable with IoVM first.

The simplest (if possibly not the best) way to get Io into your project is to copy all of the .c and .h files from the 'base', 'extras', and 'parser' directories into a single convenient location. Then, if you are not on an OS X machine, remove any OSX files from that location. If you _are_ on an OS X machine, I suspect that you should remove the corresponding non-OSX files instead. Since I don't have such a machine, I cannot verify this belief. You should also remove the files with CLI in the names, because they provide the "Command Line Interface" to the standalone Io interpreter. Since you are embedding, you do not need that interface and it will just get in your way. Remove main.c, because it contains the main() function for the standalone interpreter. At least in the version that was current when this guide was written, you may need to remove SplayTrees files, because they cause linkage problems.

Now add all of the remaining files to your project compiling system. This may mean adding them all to a make file, or to a Visual C++ project, or typing something like 'CC MyApp.c Io/*.c' (where MyApp.c represents your other files and Io is the "convenient location" mentioned above). Attempt to compile, and resolve any remaining issues. Sorry for not being more specific, but they are pretty obvious -- #includes can be adjusted so that they can find the files they want, or you can adjust your INCLUDE settings for your build process to point to better directories.

Now you'll need a file with your main() in it. Here is the simplest one I can imagine that will actually work. In my simple project, I just dumped all of the Io files in the same directory as my application, because my application only required a single file. You'll probably want to have a bit more structure in your own projects. I only mention it because the #includes that I give below will require some modification to match the location you used for the Io source code.

    #include "IoState.h"

    IoState * state = NULL;

    void SimplePrint(IoState * state, char * s)
    {
    	puts(s);
    }

    void SimpleException(IoState * state, IoException * e)
    {
    	printf("Exception: %s - %s\n",
    		IoException_name(e),
    		IoException_description(e));
    }

    void SimpleExit(IoState * state)
    {
    	IoState_free(state);
    	state = NULL;

    	exit(0);
    }

    int main(int argc, const char* argv[])
    {
    	IoObject *result;
    	state = IoState_new();
    	IoState_callbackContext_(state, NULL);
    	IoState_printCallback_(state, SimplePrint);
    	IoState_exceptionCallback_(state, SimpleException);
    	IoState_exitCallback_(state, SimpleExit);

    	char buf[1000]; /* amazingly bad style -- buffer overruns */
    	buf[0] = 0;

    	while (true)
    	{
    		printf(":");
    		gets(buf);
    		result = IoState_doCString_(state, buf);
    		printf("-=> ");
    		IoObject_print(result);
    	}

    	/* You can't get here...  Typing 'exit' takes you to
    	   SimpleExit, above */
    	return 0;
    }

That's it. That's really not too bad for the main program of an interactive language processor. Printing, exception handling, and exiting are handled by the supplied callbacks. They're not fancy, but they get the job done. When you run it, just type in single Io lines and they will be evaluated. Typing 'exit' will activate the Lobby's 'exit' slot, which is bound to the SimpleExit() callback, which will terminate the program. Depending on the role of Io in your application, you will almost certainly want more useful handling of exceptions and exits to keep bad scripts from terminating your program.

I feel I should point out a few things at this point. First, if you are a C++ programmer, you may have already encountered some linkage problems. If so, replace the first line, which says:

    #include "IoState.h"

    with the following:

    extern "C"
    {
    	#include "IoState.h"
    }

The 'extern "C"' block tells the compiler to use C rules for processing function names instead of C++ rules. Since all of the Io source is in .c files, they were probably compiled with C rules, and the linker will not be able to find them if you #include their declarations in C++ mode.

Secondly, all of the callback functions you've seen here are special callbacks intrinsically supported by Io. This is _not_ how you will be adding your own functions, which I will be covering in Steps 1 and 2.

Lastly, once you have the above functioning and you understand what it is doing, you should probably read the original sample code from the embedding section of the Programming Guide. There is also a C++ translation of that sample code available on the Io website, if you find C++ easier to read.

## Step 1 : Binding your first function

First, write your function. All C-side functions will have the same signature, which is:

    IoObject * SomeFunc(IoObject * self, IoObject * locals, IoMessage * m)

(Steve : PLEASE correct whatever misconceptions I may have about the following...) self provides the Io object that is receiving this message. locals provides the Io object that contains all of the local variables available to the message. m provides the message itself : name, arguments, etc.

Let's make a function that takes two numbers, adds them, and returns the result:

    IoObject * MyAdd(IoObject *self, IoObject *locals, IoMessage *m)
    {
    	// Because we registered MyAdd using the Lobby as "self"
    	// (see Controller::main below), we should receive the Lobby
    	// back as "self".
    	double a = IoMessage_locals_doubleArgAt_(m, locals, 0);
    	double b = IoMessage_locals_doubleArgAt_(m, locals, 1);

    	// This line was originally written with the IONUMBER macro
    	// from IoNumber.h, but is expanded here for clarity.
    	return IoNumber_newWithDouble_(self->tag->state, a+b);
    }

There are really only three lines of code there, and the names are fairly self explanatory. The one thing that is not immediately obvious is the call to IoNumber_newWithDouble_() at the end. This function requires an IoState* to know which IoState to build the new number object in. But all we have is an IoObject* (_not_ an IoState*). Fortunately, we can get an IoState* from any valid IoObject* through the tag and state members, as shown above. self->tag->state retrieves the IoState* in which self resides. As the comment says, the code originally used IONUMBER and just said:

    	return IONUMBER(a+b);

which is pretty smooth and easy to use. But it is useful to know what is going on behind the macros, so I showed you the full thing first.

Now we need to make this function available to our Io interpreter so that users can use it.

WARNING -- The approach taken here is _BAD_ style. I do this to create the smallest example I can of a semi-useful function. Before you start binding all kinds of stuff into the Lobby, you should really read Step 2 and examine some of the existing Io objects and code libraries. In a truly object oriented environment, like Io, procedures/functions/methods should _not_ "just exist" in a global namespace. They should be members of some object (or "messages that an object understands" might be a better phrasing :-). Step 2 will discuss creating a 'Learning' object that can contain some useful functions. If you're not sure why global naming is bad, either just trust me (not a great solution) or go find some books on object oriented thinking. "Object Oriented Software Construction, 2nd Ed" is an extremely thorough source, although it focuses on the Eiffel language and may confuse the issue if you are trying to learn Io.

Now on to the simple binding. In your main(), after you register the exit callback, insert this line:

    IoObject_addMethod_(IoState_lobby(state),
                        IoState_stringWithCString_(state, "MyAdd"),
                        MyAdd);

It's only one line of code, which is pretty simple, but there are some parts of that line that may require some explanation.

First, 'state' is the IoState* that you created earlier with IoState_new().

IoState_lobby(state) is the IoObject* representing the Lobby object of state.

IoState_stringWithCString_() was originally written as a call to IOSTRING from IoString.h. IOSTRING requires access to a 'self' variable that must be an IoObject* in the same IoState* that you are working in. The IOSTRING macro retrieves the IoState* from the IoObject* through the same self->tag->state sequence that was used inside MyAdd() above. In the code so far, we don't have a 'self' IoObject*, so the macro will fail to compile. But we do have the actual IoState* that is needed, so we directly call the underlying IoState_stringWithCString_(state, "MyAdd") to create an IoString inside the appropriate IoState*. (See, I told you it was useful to know what happened inside the macros :-)

Anyway, you're now done with Step 1. Fire up your program, and you can now issue 'MyAdd(17,25)' and see the result of 42. To reiterate that code in its most compact form:

    IoObject * MyAdd(IoObject *self, IoObject *locals, IoMessage *m)
    {
    	double a = IoMessage_locals_doubleArgAt_(m, locals, 0);
    	double b = IoMessage_locals_doubleArgAt_(m, locals, 1);
    	return IONUMBER(a+b);
    }
    /* with the following immediately after the exit callback is registered: */
    IoObject_addMethod_(IoState_lobby(state),
                        IoState_stringWithCString_(state, "MyAdd"),
                        MyAdd);

## Step 2 : Doing it right :-) (obviously still in progress)

At this point, you can probably start examining the various real examples in the Io distribution. The main thing to learn is that instead of adding things directly to the Lobby, you will create a named but empty IoObject (which will generally be in the Lobby), and then you will add methods to that object instead of directly to the Lobby. Here is a rough sketch, taken from the IoServer/Directory/IoDirectory.c file:

    IoDirectory *IoDirectory_proto(void *state)
    {
    	/* Make a blank object in the appropriate IoState */
      IoObject *self = IoObject_new(state);

    	/* Tag it -- this must be done correctly for the self->tag->state */
    	/* trick above to work for your object! */
      self->tag = IoDirectory_tag(state);

      self->data = calloc(1, sizeof(IoDirectoryData));
      DATA(self)->path = IOSTRING(".");

    	/* Tell Io how to prototype this object -- I don't understand this */
    	/* right now, but I'm working on it. */
      IoState_registerProtoWithFunc_(state, self, IoDirectory_proto);

    	/* Add all of the relevant methods to this new object */
      IoObject_addMethod_(self, IOSTRING("clone"), IoDirectory_clone);
      IoObject_addMethod_(self, IOSTRING("setPath"), IoDirectory_setPath);
      IoObject_addMethod_(self, IOSTRING("path"), IoDirectory_path);
      IoObject_addMethod_(self, IOSTRING("name"), IoDirectory_lastPathComponent);
      IoObject_addMethod_(self, IOSTRING("exists"), IoDirectory_exists);
      IoObject_addMethod_(self, IOSTRING("items"), IoDirectory_items);
      IoObject_addMethod_(self, IOSTRING("at"), IoDirectory_at);
      IoObject_addMethod_(self, IOSTRING("count"), IoDirectory_count);
      IoObject_addMethod_(self, IOSTRING("create"), IoDirectory_create);
      return self;
    }

    /* And this is from IoDirectoryInit.c */
    IoObject_setSlot_to_(context, SIOSTRING("Directory"),
    IoDirectory_proto(self));