Io Programming/Writing Addons

Writing a C Addon for Io

edit

This example will show you how to create a new Addon written in C that can be included in and built along with the Io source package. The example addon shown here is a very minimal object with one method that returns self. We will call it TrivialObject.

For an introduction to creating C++ bindings, see Binding Io to C++.

This skeleton was based on Steve Dekorte's Zlib addon sources.

Addon Structure

edit

An Io addon is comprised of a few parts:

  • build.io, which defines the addon by cloning AddonBuilder and specifying system dependencies and/or performing other pre-build operations.
  • depends, a text file containing a one-line list of other Io addons that this addon depends on, if any.
  • "protos", a text file containing a one-line list of the proto names offered by this addon.
  • io/, a directory containing your Io script to include with the addon (named YourAddon.io). If all your code is in C and you have no additional Io methods to define, as in this example, you may not need anything here.
  • source/, a directory of C and/or C++ sources and headers to build into the addon library. As above, if all your code for this addon is written in Io, you may not need anything here.
  • tests/, a directory containing a battery of tests to be run to validate this addon.

Creating the Addon

edit

Addon Structure

edit

Create these files and folders using this structure under the Io source tree:

  • io/
    • addons/
      • TrivialObject/
        • build.io
        • depends
        • io/
        • source/
          • IoTrivialObject.h
          • IoTrivialObject.c
        • tests/

Files

edit

Now write the file contents:

build.io

edit
AddonBuilder clone do(
)

For such a simple object, we don't need any dependencies, but they could be specified using:

  • dependsOnBinding(name)
  • dependsOnHeader(name)
  • dependsOnLib(name)
  • dependsOnFramework(name)
  • dependsOnInclude(name)
  • dependsOnLinkOption(name)
  • dependsOnSysLib(name)
  • dependsOnFrameworkOrLib(frameworkName, libName)

IoTrivialObject.h

edit
//metadoc copyright Your Name Here 2008

// don't forget the macro guard
#ifndef IOTrivialObject_DEFINED
#define IOTrivialObject_DEFINED 1

#include "IoObject.h"
#include "IoSeq.h"

// define a macro that can check whether an IoObject is of our type by checking whether it holds a pointer to our clone function
#define ISTrivialObject(self) IoObject_hasCloneFunc_(self, (IoTagCloneFunc *)IoTrivialObject_rawClone)

// declare a C type for ourselves
typedef IoObject IoTrivialObject;

// define the requisite functions
IoTag *IoTrivialObject_newTag(void *state);
IoObject *IoTrivialObject_proto(void *state);
IoObject *IoTrivialObject_rawClone(IoTrivialObject *self);
IoObject *IoTrivialObject_mark(IoTrivialObject *self);
void IoTrivialObject_free(IoTrivialObject *self);

// define our custom C functions
IoObject *IoTrivialObject_returnSelf(IoTrivialObject *self, IoObject *locals, IoMessage *m);

#endif

IoTrivialObject.c

edit
//metadoc TrivalObject copyright Your Name Here
//metadoc TrivalObject license BSD revised
//metadoc TrivalObject category Example
/*metadoc TrivalObject description
Describe your addon here.
*/

#include "IoState.h"
#include "IoTrivialObject.h"

// _tag makes an IoTag for the bookkeeping of names and methods for this proto
IoTag *IoTrivialObject_newTag(void *state)
{
	// first allocate a new IoTag
	IoTag *tag = IoTag_newWithName_("TrivialObject");

	// record this tag as belonging to this VM
	IoTag_state_(tag, state);

	// give the tag pointers to the _free, _mark and _rawClone functions we'll need to use
	IoTag_freeFunc_(tag, (IoTagFreeFunc *)IoTrivialObject_free);
	IoTag_markFunc_(tag, (IoTagMarkFunc *)IoTrivialObject_mark);
	IoTag_cloneFunc_(tag, (IoTagCloneFunc *)IoTrivialObject_rawClone);
	return tag;
}

// _proto creates the first-ever instance of the prototype 
IoObject *IoTrivialObject_proto(void *state)
{
	// First we allocate a new IoObject
	IoTrivialObject *self = IoObject_new(state);

	// Then tag it
	IoObject_tag_(self, IoTrivialObject_newTag(state));

	// then register this proto generator
	IoState_registerProtoWithFunc_(state, self, IoTrivialObject_proto);

	// and finally, define the table of methods this proto supports
	// we just have one method here, returnSelf, then terminate the array
	// with NULLs
	{
		IoMethodTable methodTable[] = {
		{"returnSelf", IoTrivialObject_returnSelf},
		{NULL, NULL},
		};
		IoObject_addMethodTable_(self, methodTable);
	}

	return self;
}

// _rawClone clones the existing proto passed as the only argument
IoObject *IoTrivialObject_rawClone(IoTrivialObject *proto)
{
	IoObject *self = IoObject_rawClonePrimitive(proto);
	// This is where any object-specific data would be copied
	return self;
}

// _new creates a new object from this prototype
IoObject *IoTrivialObject_new(void *state)
{
	IoObject *proto = IoState_protoWithInitFunction_(state, IoTrivialObject_proto);
	return IOCLONE(proto);
}

// _mark is called when this proto is marked for garbage collection
// If this proto kept references to any other IoObjects, it should call their mark() methods as well.
IoObject *IoTrivialObject_mark(IoTrivialObject* self)
{
	return self;
}

// _free defines any cleanup or deallocation code to run when the object gets garbage collected
void IoTrivialObject_free(IoTrivialObject *self)
{
	// free dynamically allocated data and do any cleanup
}

// This is the one method we define, which does nothing but return self.
IoObject *IoTrivialObject_returnSelf(IoTrivialObject *self, IoObject *locals, IoMessage *m)
{
	// A method should always return an IoObject*
	// Per Io style guidelines, it's preferred to return self when possible.
	return self;
}

Building

edit

When make addon TrivialObject is run, first build.io is loaded and run. In our case it does nothing, but it could specify dependencies, perform system commands, or even trigger external make operations as in the case of SGML.

Next, the source tree is configured and AddonBuilder creates a new file in the source/ directory called IoTrivialObjectInit.c to serve as the entry point to the addon's library. The exact of this file depend on the number and types of files found in source/ as described below.

IoTrivialObjectInit.c

#include "IoState.h"
#include "IoObject.h"

IoObject *IoTrivialObject_proto(void *state);

void IoTrivialObjectInit(IoObject *context)
{
	IoState *self = IoObject_state((IoObject *)context);

	IoObject_setSlot_to_(context, SIOSYMBOL("TrivialObject"), IoTrivialObject_proto(self));

}

As you can see, it defines IoTrivialObject_proto(void* state) which initializes the addon by adding a new slot to the VM context named for the addon, and sets that slot to the brand new prototype created by IoTrivialobject_proto.

AddonBuilder builds an addon into the addon's _build directory. Within this directory you may find:

  • _build/
    • dll/ — Dynamic libraries are built here.
    • headers/ — Any *.h files from the source/ directory are copied here.
    • lib/ — Static libraries are built here
    • objs/ — .o/.obj files produced by the build are placed here

After building, the Io install process then copies the entire addons/ directory to its final destination (e.g. /usr/local/lib/io/addons).

Using Additional Sources

edit

In a more complicated addon, there may be multiple source files in source/ that exist for various purposes. These files are treated differently by AddonBuilder based on the format of their filenames.

  • Any file in the source directory with the extension .h will be copied to _build/headers.
  • Any source file named with the pattern Io*.c or Io*.cpp, and does not contain underscores, will be treated as source code for an Io proto.
    • These files may contain a line containing docDependsOn(Name) to specify that the source depends on the object file produced by compiling a file called IoName.c. The sources will be built in an order specified by these dependencies.
    • Each proto source files will have a corresponding entry generated in the addon's Init file: IoObject *IoFileName_proto(void *state)
  • Files named with the pattern Io*.c or Io*.cpp and containing underscores, e.g., IoExtra_source, are treated as "extra" sources, and will have a corresponding entry generated in the addon's Init file to perform their initializations: void IoExtra_sourceInit(void *context)
  • Any source file that contains "Init" in its name will be ignored, except for the automatically-generated Init file for this addon.
  • All other files in the directory are ignored by AddonBuilder.

Testing

edit

Validation tests for your addon can be written and then run using make testaddons.

To write a test:

  • Create a file named run.io in the tests/correctness/ directory of your addon tree to find and run any unit tests in the same directory.
    TestSuite clone setPath(System launchPath) run
  • Create test scripts under tests/correctness which clone UnitTest and use assertions to validate your conditions.