Next: , Previous: Kite Language Specification, Up: Top


4 Extending Kite

There are two ways to extend Kite: write your own classes/packages and writing your own compiled extensions.

4.1 Kite Packages

To write a package, it is a simple matter of saving your classes and other code in a file with the .kt extension. For example, here is a simple package:

     #!/usr/bin/kite
     
     class Greeting [
         property text,
         method sayGreeting() [
             text|print;
         ]
     ];
     
     class Hello from Greeting [
         construct [
             this.text = "Hello!";
         ]
     ];
     
     class Hola from Greeting [
         construct [
             this.text = "¡Hola!";
         ]
     ];

We shall call this Greetings.kt. Now, where should this be placed? We know that greetings fall under common sayings, so let's place this in the CommonSayings folder. Now, the package can be referenced by using import CommonSayings.Greetings in any Kite application. Let's try it:

     #!/usr/bin/kite
     
     import "CommonSayings.Greetings";
     greetings = CommonSayings.Greetings;
     
     english = make greetings.Hello;
     spanish = make greetings.Hola;
     
     english|sayGreeting;
     spanish|sayGreeting;

Assuming that your folder structure looks like this:

     ./CommonSayings/
     ./CommonSayings/Greetings.kt
     ./test.kt

Running kite test.kt will produce:

     Hello!
     ¡Hola!

Easy enough, but what if your package isn't in the current Kite search path? You'll have to use something like this to fix that:

     #!/usr/bin/kite
     
     import "System.vm.loader";
     System.vm.loader.searchPath = System.vm.loader.searchPath + ":/path/to/package";
     greetings = System.vm.loader|loadClass("CommonSayings.Greetings");
     
     english = make greetings.Hello;
     spanish = make greetings.Hola;
     
     english|sayGreeting;
     spanish|sayGreeting;

Now, it'll work as intended.

Caution: import will only load a class once during runtime. If you need to reload the package for any reason, you'll currently need to restart Kite. (This is on the TODO for future releases.)

4.2 Compiled Extensions

Compiled extensions are the second route to extending Kite. There are two routes to doing this: native extensions and using the interface.language.c class.

4.2.1 Native Kite extensions

Native Kite extensions allow a user to write a Kite module in the C programming language (or any language that can export a C interface). This is useful for those that require extra speed, or to add support for libraries and modules that were originally written in C or other programming languages.

4.2.1.1 A Simple Example: Greeting in C

Here is a simple example of a Kite module in C. We want to have a method called "write" that outputs a greeting to the user on standard output. Let's create a file called "greeting.c" with the following lines:

     #include <stdio.h>
     #include <stdlib.h>
     #include <kite_object.h>

kite_object.h is mandatory in order to use Kite language features. Now, we need to write the method. We'll call it Greeting_write, although it can be called anything:

     KITE_CLASS_METHOD(Greeting_write)
     {
         KITE_NO_ARGS;
         KITE_THIS_NOT_USED;
         printf("Hello, world!\n");
         kite_vm_return(thd, kite_new_null(thd));
     }

KITE_NO_ARGS and KITE_THIS_NOT_USED are helper macros designed to remove spurious compile warnings. kite_vm_return is how we return values to the user; all code generally returns some value, even if it's simply null. As you can see, this method simply writes a message to standard output.

Now we need to let Kite know of this method and the object. We'll set up an initializer function, as shown here:

     KITE_MODULE_INITIALIZER(greeting)
     {
         kite_object_t *inherits =
             kite_dereference_and_load(thread, "System.object");
         kite_object_t *newclass =
             kite_new_class(thread, inherits, "greeting");
         kite_set_docstring(newclass, "Output greeting to user.");
     
         kite_add_method(thread, newclass, "write",
             kite_new_method_compiled_with_docs(thread, Greeting_write,
                                       "Write greeting to user.", 0));
     
         /* Add to parent to complete loading. */
         kite_add_property(thread, parent, "greeting", 1, NULL);
         kite_set_property(thread, parent, "greeting", newclass);
     }

The code above creates a new class that inherits System.object and contains one method called "write" that takes no arguments. It then adds the class to the parent class, which in this case, is the root class.

Now that we've written our object, we need to compile it. On Linux, we can do the following:

     gcc -shared -I/usr/local/include -fPIC -g -o greeting.so greeting.c

and write some Kite code to test:

     import "greeting";
     greeting|write;

Now, when we run Kite on the test code, we'll get:

     $ kite test.kt
     Hello, world!
     $
4.2.1.2 For Further Information

If you have Doxygen, you can generate documentation for all of the Kite internals by running doxygen Doxyfile from the top of the distribution. It should place HTML and LaTeX files in docs/html and docs/latex, respectively. A version of the Doxygen documentation can also be found here.

4.2.2 Using interface.language.c

Please note: This method only works on platforms supported by C/Invoke. At this writing (July 2008), the supported platforms are Windows (32-bit Intel), OS X (PPC and Intel) and Linux/*BSD (32 and 64-bit Intel).

interface.language.c is a module that allows one to interface with compiled modules on his or her system. No experience with C/C++ or Kite internals is necessary.

Here is a simple example of using interface.language.c:

     import "interface.language.c";
     
     libr = make interface.language.c("libm.dylib");
     libr|add_method(
     	"cos",
     	interface.language.c.param_types.double,
     	[interface.language.c.param_types.double]
     );
     libr|cos(0.0)|print;

(the example prints "1.00000")

Structures and callback functions are also supported. Here is a longer example that implements both. First, we'll create a quick and simple dynamic library called libtest. Here are the contents of libtest.c:

     #include <stdio.h>
     
     struct xyz
     {
         char *name;
         int val;
     };
     
     void print(struct xyz *ptr, int x, void(*ptr)(int))
     {
         printf("name = %s, value = %d\n", ptr->name, ptr->val);
         (*ptr)(x);
     }

To compile on Linux: gcc -shared -fPIC -g -o libtest.so libtest.c. Now here is the Kite code necessary to access our new module:

     import "interface.language.c";
     
     lib = make interface.language.c("/tmp/libtest.so");
     
     st = make interface.language.c.c_struct(
     	lib,
     	[
     		["name", interface.language.c.param_types.string],
     		["val", interface.language.c.param_types.int]
     	]
     );
     
     inst = st|make_instance;
     inst|name("test");
     inst|val(123);
     
     cb = make interface.language.c.c_callback(
         lib,
         interface.language.c.param_types.void,
         [interface.language.c.param_types.int],
         method(extra, x) [
             x|print;
         ],
         null
     );
     
     lib|add_method(
     	"print",
     	interface.language.c.param_types.void,
     	[
     	    interface.language.c.param_types.pointer,
     	    interface.language.c.param_types.int,
     	    interface.language.c.param_types.pointer
     	]
     );
     
     lib|print(inst|pointer, 31415, cb|pointer);

This results in:

     name = test, value = 123
     31415

More information on interface.language.c can be found in the kdoc documentation for the current version.

4.3 Embedding Kite In An Application

Kite can also be embedded into a compiled application. As with creating a compiled module, you'll first need to include kite_object.h:

     #include <kite_object.h>

In order to access Kite, you'll need to create a new virtual machine. Virtual machines are the fundamental unit that represents a single instance of the Kite interpreter. These can be created using the kite_new_vm function. Let's do that now inside of our main():

     int main(int argc, char **argv)
     {
         kite_vm_t *vm;
     
         kite_app_init();
         vm = kite_new_vm(argv);
         ...
         kite_free_vm(&vm);
         return 0;
     }

Also note that you'll need to call kite_app_init() before using any Kite functions. This is necessary to initialize the garbage collector, for those versions of Kite compiled with Boehm GC support.

kite_new_vm requires a single argument, a pointer to an array of strings. This represents the command-line arguments passed into Kite at the command prompt; passing argv should work fine. You'll also notice that we delete the virtual machine after we've finished using it by using the kite_free_vm function. This function sets the VM object to NULL on completion.

At this point, we have a virtual machine object, but it's not currently doing any useful work. We'd like it to run some code for us. To do that, we'll need to compile some code:

     kite_thread_t *thd = kite_vm_creator_thread(vm);
     kite_object_t *obj = kite_vm_compile_from_string(thd, "42|print;", 1);
     
     if (thd->exception)
     {
         /* Uh oh. */
         exit(-1);
     }

kite_vm_creator_thread above returns the very first thread created by the virtual machine. This will usually be the thread that initially runs any code you write. kite_vm_compile_from_string is one of the functions that can be used to compile code. This compiles a snippet of code into a Kite object and returns the object. Errors start from line 1. Other compile methods exist as well, including ones that compile from a file.

To ensure there are no errors, we check for the existence of an exception object in thd->exception. If this property is NULL, code we've run did not throw an exception. Also, most functions will return FALSE or NULL on error.

Since we now have a compiled form of the Kite code we've entered above, we can run it. To do that, we can call kite_start_bytecode:

     kite_start_bytecode(thd, obj);
     if (thd->exception) ...

The return value from executed Kite code, if any, is on the top of the running stack. This can be retrieved by using kite_vm_pop(), but we'll ignore it for our example. To make sure everything's cleaned up, we should wait for our thread to terminate:

     kite_dereference_object(obj);
     kite_join_thread(vm, thd);

kite_dereference_object() is an important function when it comes to Kite memory management. It decrements the reference count of a given object, and when the reference count reaches zero, it is scheduled for deletion. The destructor is generally called when this occurs. If you need to pass references to Kite objects around, you should always increment the reference count by using the analogous kite_reference_object() function.

Your file should now look like this:

     #include <kite_object.h>
     
     int main(int argc, char **argv)
     {
         kite_vm_t *vm;
         kite_thread_t *thd;
         kite_object_t *obj;
     
         kite_app_init();
     
         vm = kite_new_vm(argv);
         thd = kite_vm_creator_thread(vm);
         obj = kite_vm_compile_from_string(thd, "42|print;", 1);
     
         if (thd->exception)
         {
             /* Uh oh. */
             exit(-1);
         }
     
         kite_start_bytecode(thd, obj);
         if (thd->exception) exit(-1);
     
         kite_dereference_object(obj);
         kite_join_thread(vm, thd);
     
         kite_free_vm(&vm);
         return 0;
     }

Compile in Linux using something like this:

     gcc -g -L/usr/local/lib -I/usr/local/include -lkite -fPIC -o example example.c

and run like this:

     $ ./example
     42
     $

And there you have it! For more information on the kinds of functions available to you, see the documentation for the Kite API, especially kite_object.h and kite_vm.h.