
SPL C-API Documentation
=======================

SPL has a pretty simple C interface which allows easy embedding of the SPL
virtual machine and the SPL compiler in other applications and extending
SPL using functions and modules written in C.

A very complete example program (using almost the entire API) is "splrun.c". It
is the tool which is usually used to run SPL programms on the command line.

A rather short example for executing SPL code is "examples/c-api-test4.c".
Beware of the other C API demo programms in "examples/": They are test cases
for fiddling with SPL byte code and SPL assembler code. Usually one does not
want to use SPL on this level.

A nice example for writing SPL modules in C is "spl_modules/mod_termio.c".

All SPL data structures and public functions are defined in the "spl.h" header
file.


Running SPL Scripts
-------------------

Running an SPL Script is easy: The SPL compiler compiles the SPL Script to
SPL Assembler commands. But this assembler commands are not genereted as text
ouput, they are passed to an SPL Assembler instance. Optionally it is possible
to run the SPL optimizer over the generated assembler code:

	char *spl_source = "debug 'Hello World!';";

	struct spl_asm *as = spl_asm_create();

	if (spl_compiler(as, spl_source, "spl_script", spl_malloc_file, 1)) error();
	spl_asm_add(as, SPL_OP_HALT, 0);
	spl_optimizer(as);

The first two argument to spl_compiler() are the SPL Assembler instance and the
SPL Source to be compiled. The third parameter is the name of the script file
(this is primarly used for error messages). The fourth parameter is a function
which can be used by the compiler for loading external files. Loading external
files is not allowed if this is a NULL pointer. The last parameter is a bool
value which specifies if the compiler should generate debug symbols.

The SPL Assembler instance can then be used to dump SPL bytecode. This
bytecode is then passed to a newly generated task of an SPL Virtual Machine:

	struct spl_vm *vm = spl_vm_create();
	struct spl_task *task = spl_task_create(vm, "main");

	spl_task_setcode(task, spl_asm_dump(as));
	task->code->id = strdup("spl_script");
	spl_asm_destroy(as);

Before the script is executed it is required to configure the SPL Virtual
Machine. Usually one wants to activate the standard builtin functions and
set a search path for loading additional SPL Modules:

	spl_builtin_register_all(vm);
	asprintf(&vm->path, ".:./spl_modules:%s", spl_system_modules_dir());

When it should be possible to execute SPL callbacks from C funktions then
one also needs to define a runloop function which should be used for that
purpose. Usually the pre-defined 'spl_simple_runloop' function is used for
that:

	vm->runloop = spl_simple_runloop;

Now it is possible to execute the script by calling spl_exec() until the HALT
opcode is reached. This opcode removes the code page from the current task
because there is nothing left to be executed on the page:

	while ( task->code ) {
		spl_gc_maybe(vm);
		task = spl_schedule(task);
		if ( spl_exec(task) < 0 ) break;
	}

The spl_gc_maybe() function runs the garbage collector when it is time to do
so. The spl_schedule() function does the scheduling between the tasks. For a
single-threaded script like in this example it is a NO-OP.

Finally cleaning up all the SPL data structures is easy. Simply destroying the
SPL Virtual Machine also destroys everything which is connected to it
(including stuff such as unloading modules loaded by the script):

	spl_vm_destroy(vm);

Have a look at "splrun.c" in the SPL source tree for a very complete example
of an SPL runtime implementation.

An application which is embedding SPL should be compiled and linked with the
options printed by "spl-config --cflags", "spl-config --ldflags" and
"spl-config --ldlibs".


Writing SPL functions in C
--------------------------

We will start with an example. In this example we create two new SPL functions
named 'myadd' and 'mysub' which implement integer addition and substraction.
First we need to create a C function which implements myadd() and mysub():

	struct spl_node *spl_builtin_myaddsub(struct spl_task *task, void *data)
	{
		int a = spl_clib_get_int(task);
		int b = spl_clib_get_int(task);

		if (!strcmp(data, "add"))
			return SPL_NEW_INT(a + b);

		if (!strcmp(data, "sub"))
			return SPL_NEW_INT(a - b);

		return 0;
	}

We also could have written two small C functions for the two SPL functions,
but then we would have had no example for the second parameter to SPL C
functions.

All SPL C functions have the same prototype: They return an SPL node pointer
and expect an SPL task struct as first and a void pointer as second argument.

An SPL node pointer is the abstract representation of a "value" in SPL. The
task struct contains all information about the currently running SPL task.

Functions such as 'spl_clib_get_int(task)' can be used to pop the arguments
from the VM stack. Arguments are pushed from right to left on the machine stack
so the first argument must be popped first, then the second, and so on. Scalar
values can be popped using the three functions:

	int spl_clib_get_int(struct spl_task *task);
	double spl_clib_get_float(struct spl_task *task);
	char *spl_clib_get_string(struct spl_task *task);

You must not free the pointer returned by
spl_clib_get_string() it is automatically freed as soon as control is passed
back to the SPL virtual machine. You also must not modify the string.

In addition to that the three functions

	int spl_clib_get_argc(struct spl_task *task);
	struct spl_node *spl_clib_get_hargs(struct spl_task *task);
	struct spl_node *spl_clib_get_node(struct spl_task *task);

can be used to get the number of remaining arguments on the VM stack, get the
hash with the named arguments or pop an argument as SPL node from the stack
respectively. The spl_clib_get_node() function is only needed when complex
(non-scalar) data structures are passed as parameters. The return values of
spl_clib_get_hargs() and spl_clib_get_node() must be freed using spl_put() (see
the seperate section about SPL data structures below).

New SPL nodes for scalar values can be created using the helper functions

	struct spl_node *SPL_NEW_INT(int v);
	struct spl_node *SPL_NEW_FLOAT(double v);

	struct spl_node *SPL_NEW_STRING(char *v);
	struct spl_node *SPL_NEW_STRING_DUP(const char *v);
	struct spl_node *SPL_NEW_SPL_STRING(struct spl_string *v);

The SPL_NEW_STRING() function expects the parameter to be already malloced. The
SPL_NEW_STRING_DUP() function is internally using strdup() to create a seperate
malloced copy of the string. SPL is internally using binary trees with
reference counters for representing strings. That improves the performance of
string concatenations massively. If you have already created an SPL string
you can easily create a value for it using the SPL_NEW_SPL_STRING() function.

It is also possible to return a NULL pointer in a SPL C function. This is
automatically converted to an 'undef' value in SPL.

Finally the new functions need to be registered with the virtual machine. This
is done using the spl_clib_reg() function:

	spl_clib_reg(vm, "myadd", spl_builtin_myaddsub, "add");
	spl_clib_reg(vm, "mysub", spl_builtin_myaddsub, "sub");

The first parameter is the SPL VM, the second the name of the function and the
third a pointer to the C function implementing the SPL function. The last
parameter (a void pointer) is simply passed as second parameter to the C
function implementing the SPL function whenever it is called. This way our
spl_builtin_myaddsub() function can distinguish if it has been called as
myadd() or mysub().

A good place for the spl_clib_reg() calls is right after calling
spl_builtin_register_all() for the virtual machine.


Writing SPL Modules in C
------------------------

Extending SPL as described above is only possible when embedding SPL in
own projects. But usually one is using an already existing SPL runtime
environment and simply wants to add a few functions. This can be done by writing
SPL modules. Here is the sourcecode of a simple example module, mod_hello.c:

	#include <spl.h>
	#include <stdio.h>

	static struct spl_node *handler_hello(struct spl_task *task, void *data)
	{
		printf("Hello World!\n");
		return 0;
	}

	void SPL_ABI(spl_mod_hello_init)(struct spl_vm *vm,
			struct spl_module *mod, int restore)
	{
		spl_clib_reg(vm, "hello", handler_hello, 0);
	}

	void SPL_ABI(spl_mod_hello_done)(struct spl_vm *vm, struct spl_module *mod)
	{
		return;
	}

The SPL_ABI() is a macro which add a little prefix with the SPL ABI version to
the identifier passed as argument. This ensures that a SPL runtime never loads
a module which has been compiled for another SPL ABI. Every module must export
the functions 'spl_mod_<module-name>_init' and 'spl_mod_<module-name>_done'.

The _init function is called when a virtual machine loads the module and the
_done function is called when a virtual machine which has loaded the module
is going to be destroyed.

The third argument to the _init function is set to 1 when the module is loaded
while restoring a dumped session. When a module is e.g. creating SPL variables
when loaded they get dumped and restored automatically and so do not need to
be recreated when the module is loaded while restoring the session.

Compiling and loading the module is straight forward:

	$ gcc -shared mod_hello.c -o mod_hello.so

	$ splrun -q 'load "hello"; hello();'
	Hello World!

Note that the module *.so file must be named mod_<module-name>.so and that the
<module-name> in the filename and the _init and _done functions must be
identical.


SPL Data Structures
-------------------

All SPL values are stored in SPL nodes (struct spl_node). Basically the
following data structure is associated with an spl_node:

	- scalar values (string, integer and floating point)
	- a hash with ordering information with references to other nodes
	- a context and a class pointer (also references to other nodes)
	- the context type information (function context, object, etc.)
	- a code pointer (reference to a code page and an address in it)
	- a flags field (additional type information and various other stuff)
	- data for hosted nodes (see seperate section below)
	- some internal data (e.g. for garbage collection)

Most of this information is not accessed directly but using helper functions.
We have seen already the helper functions for creating new SPL nodes with
scalar values. The helper functions for reading the scalar value from an
existing node are:

	int spl_get_int(struct spl_node *node);
	double spl_get_float(struct spl_node *node);
	char *spl_get_string(struct spl_node *node);

	struct spl_string *spl_get_spl_string(struct spl_node *node);
	char *spl_get_value(struct spl_node *node);
	int spl_get_type(struct spl_node *node);

The first three functions are self explainatory. While spl_get_string() returns
a single continous string (you must not free or modify this string),
spl_get_spl_string() returns the spl_string structure used internally in SPL to
store strings. The spl_get_value() function does the same as the
spl_get_string() function but returns a NULL pointer when the value is
undefined (spl_get_string() return "" in this case). The spl_get_type() is used
by the dynamically typed operators to decide which typed operator should be
used and returns one of SPL_TYPE_NONE, SPL_TYPE_INT, SPL_TYPE_FLOAT or
SPL_TYPE_OBJ. Usually SPL_TYPE_NONE is interpreted as if SPL_TYPE_INT were
returned.

SPL is using a hybrid garbage collector which also performs reference counting.
Thus one needs to update the reference counters whenever creating new or
removing old references for an SPL node. This can be done using the following
two functions:

	struct spl_node *spl_get(struct spl_node *node);
	void spl_put(struct spl_vm *vm, struct spl_node *node);

The spl_get() function creates a new node when called with a NULL pointer as
argument. The argument 'vm' to spl_put() is required for the garbage collector.
For this and simmilar reasons most functions for working with SPL nodes require
either a pointer to the SPL virtual machine or a pointer to the currently
running SPL task as argument. SPL nodes generated with one of the SPL_NEW_*()
functions described above have their reference counter already set to one. So
it is wrong to make an additional call to spl_get() for these nodes unless
you are creating more than one link to the new node.

Each SPL node may have child nodes. Such child nodes can be created, looked up
and removed using the following three functions:

	struct spl_node *spl_create(struct spl_task *task,
			struct spl_node *node, const char *key,
			struct spl_node *newnode, int flags);

	struct spl_node *spl_lookup(struct spl_task *task,
			struct spl_node *node, const char *key, int flags);

	void spl_delete(struct spl_task *task,
			struct spl_node *node, const char *key);

The 'flags' argument to spl_create() and spl_lookup() are bitmap. The following
values are supported by both functions:

	SPL_LOOKUP_TEST
		Do not trigger a runtime error when the entry looked for can
		not be found. This is example given used by the SPL 'declared'
		statement.

	SPL_LOOKUP_NOCTX
	SPL_LOOKUP_NOSTATIC
		These are used internally in recursive spl_lookup() calls,
		example given when looking something up in the class
		derivation path.

In addition to the SPL_LOOKUP_TEST flag the following flags are supported by
the spl_create() function:

	SPL_CREATE_LOCAL
		Create it directly here. Do not follow the context pointers
		and skip the local blocks. Do not trigger a runtime error
		when the variable does not exist yet. In most cases one needs
		to pass this flag.

	SPL_CREATE_BEGIN
		When creating a new key, create it at the begin of the key
		list. Without this flag new keys are added to the end of the
		key list.

	SPL_CREATE_NOSTATIC
		Used internally.

	SPL_CREATE_FUNCLOCAL
		Create function local unless already defined in a local
		command block. This is used internally for storing regular
		expression results.

The following two functions from the "time" module are used to convert a
UNIX 'tm' struct to an SPL data structure and vice versa. I think this is
an excellent example for using spl_lookup() and spl_create():

	static void convert_node_to_tm(struct spl_task *task, struct spl_node *node, struct tm *ttm)
	{
	    memset(ttm, 0, sizeof(struct tm));
	    ttm->tm_sec   = spl_get_int(spl_lookup(task, node, "sec",   SPL_LOOKUP_TEST));
	    ttm->tm_min   = spl_get_int(spl_lookup(task, node, "min",   SPL_LOOKUP_TEST));
	    ttm->tm_hour  = spl_get_int(spl_lookup(task, node, "hour",  SPL_LOOKUP_TEST));
	    ttm->tm_mday  = spl_get_int(spl_lookup(task, node, "mday",  SPL_LOOKUP_TEST));
	    ttm->tm_mon   = spl_get_int(spl_lookup(task, node, "mon",   SPL_LOOKUP_TEST));
	    ttm->tm_year  = spl_get_int(spl_lookup(task, node, "year",  SPL_LOOKUP_TEST));
	    ttm->tm_wday  = spl_get_int(spl_lookup(task, node, "wday",  SPL_LOOKUP_TEST));
	    ttm->tm_yday  = spl_get_int(spl_lookup(task, node, "yday",  SPL_LOOKUP_TEST));
	    ttm->tm_isdst = spl_get_int(spl_lookup(task, node, "isdst", SPL_LOOKUP_TEST));
	    spl_put(task->vm, node);
	}

	static struct spl_node *convert_tm_to_node(struct spl_task *task, struct tm *ttm)
	{
	    struct spl_node *result = spl_get(0);
	    spl_create(task, result, "sec",   SPL_NEW_INT(ttm->tm_sec),   SPL_CREATE_LOCAL);
	    spl_create(task, result, "min",   SPL_NEW_INT(ttm->tm_min),   SPL_CREATE_LOCAL);
	    spl_create(task, result, "hour",  SPL_NEW_INT(ttm->tm_hour),  SPL_CREATE_LOCAL);
	    spl_create(task, result, "mday",  SPL_NEW_INT(ttm->tm_mday),  SPL_CREATE_LOCAL);
	    spl_create(task, result, "mon",   SPL_NEW_INT(ttm->tm_mon),   SPL_CREATE_LOCAL);
	    spl_create(task, result, "year",  SPL_NEW_INT(ttm->tm_year),  SPL_CREATE_LOCAL);
	    spl_create(task, result, "wday",  SPL_NEW_INT(ttm->tm_wday),  SPL_CREATE_LOCAL);
	    spl_create(task, result, "yday",  SPL_NEW_INT(ttm->tm_yday),  SPL_CREATE_LOCAL);
	    spl_create(task, result, "isdst", SPL_NEW_INT(ttm->tm_isdst), SPL_CREATE_LOCAL);
	    return result;
	}

The 'keys' used in spl_create() and spl_lookup() are simple strings in which
the dot character can be used as seperator for recursive lookups. For example the following spl_lookup() call looks up the variable 'A', then inside
of 'A' the variable 'B' and finally inside of that 'B' the variable 'C':

	spl_lookup(task, node, "A.B.C", 0);

In addition to that there are some special keys starting with the '!' prefix
(for example '!CLS' looks up the parent class of a class or object) or the
'#' prefix (special variables generated by the compiler). The following
two functions can be used to encode everything which is not a number, 7-bit
ascii letter or underscore and decode such an encoded again:

	char *spl_hash_encode(const char *source);
	char *spl_hash_decode(const char *source);

The algorithm of spl_hash_encode() is also used when hash elements are
addressed using square brackets in SPL. The return value of this
function is a malloced string which must be freed by the caller.


SPL Strings
-----------

SPL stores strings in spl_string structs. Such a struct has a reference counter
and (optionally) a left child, a right child and a text value. The string
represented by such an spl_string struct is the concatenation of the string
represented by the left child, the text value and the string by the right
child. The following function can be used to create a new string struct:

	struct spl_string *spl_string_new(int flags,
			struct spl_string *left, struct spl_string *right,
			char *text, struct spl_code *code);

The 'flags' argument is a bitmap for which the following values are supported:

	SPL_STRING_STATIC
		Usually the 'text' argument is a malloced pointer which is
		automatically freed when the spl_string struct is destroyed.
		This flag must be set when the SPL string subsystem should
		not free the string.

	SPL_STRING_DOTCAT
		Automatically insert a dot character between the left child
		and the text value. This is used to speed up the creation
		of variable lookup paths (see spl_lookup() above).

	SPL_STRING_UTF8
		Indicates that the string contains UTF8 characters. This flag
		is automatically maintained by the strings library and does not
		need to be passed to spl_string_new().

	SPL_STRING_NOAUTOGET
		Do not increment the reference counters of the children.

The 'left' and 'right' arguments do specify the left and right children and
must be NULL when no left or right child exists.

The 'code' argument is used when the 'text' pointer points to the data section
of a code page and the reference counter of the code page should be decremented
when this spl_string struct gets destoyed. This argument is usually simply
set to NULL.

Often the 'text' argument is dynamically created. There is a printf-like
helper function for automatically allocating the text argument and filling
it with the string sprintf() would generate:

	struct spl_string *spl_string_printf(int flags,
			struct spl_string *left, struct spl_string *right,
			const char *fmt, ...);

Sometime one needs to manually increment or decrement the reference counter
of an SPL string. This can be done using the following two functions:

	struct spl_string *spl_string_get(struct spl_string *s);
	void spl_string_put(struct spl_string *s);

Finally there is a function for generating the flat string representation of
an SPL string:

	char *spl_string(struct spl_string *s);

This function stores the flat representation of the string as the new text
value and dereferences the children. That also means that the caller must
not modify or free the return value of spl_string().


Hosted SPL Nodes
----------------

Lets have a look at the following SPL/SDL (SDL is the "Simple DirectMedia
Library") example program:

	load "sdl";
	
	sdl_init(640, 480, fullscreen: 0);
	sdl_title("Sprites Demo");
	
	var sprite_background = sdl_sprite_create();
	var sprite_ball       = sdl_sprite_create();
	
	sprite_background.image = sdl_image_load("background.png");
	sprite_ball.image       = sdl_image_load("ball.png");
	
	while (1)
	{
	    sprite_ball.x = (sprite_ball.x + 5) % 640;
	    sprite_ball.y = (sprite_ball.y + 1) % 480;
	
	    sdl_sprite_update();
	    sdl_delay(25);
	}

You can easily see that the SPL nodes pointed to by the variable names
'sprite_background' and 'sprite_ball' are somewhat special. Changing the
values of the member variables 'x' and 'y' have the side effect of changing
the position of the sprite on the screen. If you play with it a bit you will
also find some other unusal effects. For example it is not possible to
create new member variables of this structures and the "x" and "y" members
seem to only accept integer values.

The sdl_sprite_create() function creates a so-called "Hosted Node", an SPL
node that is not a freestanding general purpose data structure but has a
close connection to a set of SPL functions or an SPL module. Here is the
C sourcecode of the sdl_sprite_create() function (with the parts which are
specific to the SDL module and not interesting here removed):

	static struct spl_node *handler_sdl_sprite_create(struct spl_task *task, void *data)
	{
	    struct sdl_sprite_hnode_data *hnd =
	            calloc(1, sizeof(struct sdl_sprite_hnode_data));
	    
	    /* initialize hnd */
	    
	    struct spl_node *n = SPL_NEW_STRING_DUP("SDL Sprite");
	    n->hnode_name = strdup("sdl_sprite");
	    n->hnode_data = hnd;
	    
	    return n;
	}

So a hosted node is an ordinary SPL node with the attribute "hnode_name" set
to an identifier specifying the node type, in this case "sdl_sprite". The
same naming scheme which applies to global variable and function names does
also apply for hnode identifiers.

The spl_node attribute "hnode_data" is a void pointer which can be freely
used by the hnode implementation to store local data.

The SDL module must also register a handler function for "sdl_sprite" nodes.
This is done using the following function call in the modules _init function:

	spl_hnode_reg(vm, "sdl_sprite", handler_sdl_sprite_node, 0);

The function prototype of handler_sdl_sprite_node() reads:

	void handler_sdl_sprite_node(struct spl_task *task,
			struct spl_vm *vm, struct spl_node *node,
			struct spl_hnode_args *args, void *data);

The last argument to spl_hnode_reg() is simply passed through to the
handler_sdl_sprite_node() function (the 'data' argument). The handler
function is called whenever an action is performed on an "sdl_sprite"
hnode. The action itself is passed using a spl_hnode_args struct and
return values are also passed back using that struct:

	struct spl_hnode_args {
		int action;
		const char *key;
		int flags;
		struct spl_node *value;
	};

The action field specifies the kind of action that should be performed.
The meaning of the other fields depends on the action. Following actions
are defined:

	SPL_HNODE_ACTION_LOOKUP
		Lookup a member variable of the hnode. The "key" field contains
		the name of the member variable and the "flags" field the value
		of the flags argument to spl_lookup(). The value of the member
		should be passed back using the "value" field.

	SPL_HNODE_ACTION_CREATE
		Like SPL_HNODE_ACTION_LOOKUP but the "value" field contains the
		value to be set for the member.

	SPL_HNODE_ACTION_DELETE
		Deletion of a member has been requested. The "key" field
		contains the name of the member that should be deleted.

	SPL_HNODE_ACTION_NEXT
	SPL_HNODE_ACTION_PREV
		For listing the member variables. The "key" field contains
		the name of a member. The "value" field should be set to a
		spl node as returned by SPL_NEW_STRING() which contains the
		name of the next or preview member respectively.

		When the "key" field is a null pointer the first member should
		be returned for SPL_HNODE_ACTION_NEXT and the last member for
		SPL_HNODE_ACTION_PREV.

	SPL_HNODE_ACTION_COPY
		Someone tries to copy the hosted node using the ':=' operator.
		Create a full copy of this node and return the new hosted node
		in the "value" field.

	SPL_HNODE_ACTION_PUT
		This node is going to be destroyed. Free all resources
		which are connected to this node.

	SPL_HNODE_ACTION_DUMP
		The virtual machine state is going to be dumped. Serialize
		the state of this node and set the spl_node attribute
		'hnode_dump' to a string representing the serialized node.

		Be prepared that this handler can later be called with
		'hnode_data' beeing a NULL pointer. Then the node status must
		be restored from the string in 'hnode_dump'.

Hosted nodes can be created so they look pretty similar to normal SPL data
structures (as the SDL sprites described above) or very different (e.g. XML
document handlers can be used like hashes with XPath queries as key values).

Most hosted nodes do not implement all of the actions described above. E.g.
the NEXT and PREV actions are rarely implemented. Sometimes it is too difficult
or even impossible to implement dump and restore. In these cases it is also
possible to not implement SPL_HNODE_ACTION_DUMP which then triggers a runtime
error when one tries to dump a virtual machine with active hosted nodes
without a valid 'hnode_dump' attribute.


Error Handling
--------------

There are two ways of error reporting in SPL: Creating VM runtime errors,
warnings or debug output and throwing exceptions. In SPL scripts the runtime
messages can be produced using the 'panic', 'warning' and 'debug' statements
and exceptions can be thrown using the 'throw' statement.

VM runtime errors, warnings and debug output can be created using the
spl_report() function. Example given:

	spl_report(SPL_REPORT_RUNTIME, task, "This is an error!\n");

	spl_report(SPL_REPORT_RUNTIME|SPL_REPORT_WARNING, task,
			"This is a warning message!\n");

	spl_report(SPL_REPORT_RUNTIME|SPL_REPORT_DEBUG, task,
			"Did you know that %d + %d is %d?\n", 23, 42, 23+42);

The 1st spl_report() call in the example creates a runtime error message. The
printed error message will contain a stack backtrace and the virtual machine 
stops execution.

The 2nd spl_report() call in the example creates a runtime warning message. The
printed message will contain a stack backtrace. But the virtual machine will
continue execution as if nothing special happened.

The 3rd spl_report() call in the example creates a debug message. The printed
message will not contain a stack backtrace and the virtual machine will
continue execution.

Note that spl_report() supports the well-known printf format strings.

Exceptions can be thrown using the spl_clib_exception() function. The following
small example module (mod_exdemo.c) implements the function exdemo() which
can throw an exception.

	#include <spl.h>
	
	static struct spl_node *handler_exdemo(struct spl_task *task, void *data)
	{
	    int argc = spl_clib_get_argc(task);
	
	    if (argc != 4)
	        spl_clib_exception(task, "ExdemoEx",
	            "description",
	            SPL_NEW_SPL_STRING(spl_string_printf(0, 0, 0,
	                "You passed %d arguments to exdemo().\n"
	                "This function must be called with 4 arguments.",
	                argc)),
	            NULL);
	
	    return 0;
	}
	
	void SPL_ABI(spl_mod_exdemo_init)(struct spl_vm *vm,
			struct spl_module *mod, int restore)
	{
	    if (!restore)
	        spl_eval(vm, 0, strdup(mod->name), "object ExdemoEx { }");
	
	    spl_clib_reg(vm, "exdemo", handler_exdemo, 0);
	}

SPL exceptions are objects and thus we first need to create a class for our
example exceptions. This is simply done using spl_eval() in the modules _init
function. It is important that this spl_eval() is not executed when a dumped
vm machine state is restored. Note that the ExdemoEx class has no properties
at all. This is not unusual for exception classes.

The handler_exdemo() function does throw exceptions when it is not called with
4 arguments. The exceptions are thrown using the spl_clib_exception() helper
function. This function is called with the current task pointer and the name
of the exception class followed by a variable number of arguments. This
additional arguments are pairs of a string pointer holding a property name
and an SPL node pointer with the value for this property. The list is
terminated with a single NULL pointer. In our example we only set the
"description" property. This property is automatically included in the
'uncaught exception' error messages:

	$ gcc -shared mod_exdemo.c -o mod_exdemo.so

	$ splrun -q 'load "exdemo"; exdemo(1, 2, 3);'
	SPL Runtime Error: Thrown uncaught exception: [ ExdemoEx ]
	>> You passed 3 arguments to exdemo().
	>> This function must be called with 4 arguments.
	in:         ROOT (byte 28 in code block 'command line')

The spl_clib_exception() helper is not 100% equal to the SPL 'throw'
instruction. The most important difference is that it does not run the
constructor for the new exception object.


Callbacks from C to SPL
-----------------------

Sometimes one needs to pass an SPL callback function to a C function and then
call this callback function from the C code. This is tricky, but possible:

	#include <spl.h>
	
	static struct spl_node *handler_callback(struct spl_task *task, void *data)
	{
	    struct spl_node *callback = spl_clib_get_node(task);
	    struct spl_node *arg1 = spl_clib_get_node(task);
	    struct spl_node *arg2 = spl_clib_get_node(task);
	    
	    struct spl_task *cb_task = spl_clib_callback_task(task->vm);
	    
	    struct spl_asm *as = spl_asm_create();
	    spl_asm_add(as, SPL_OP_PUSHC, "retval");
	    spl_asm_add(as, SPL_OP_ZERO,  0);
	    spl_asm_add(as, SPL_OP_PUSHA, "arg2");
	    spl_asm_add(as, SPL_OP_PUSHA, "arg1");
	    spl_asm_add(as, SPL_OP_DCALL, "callback");
	    spl_asm_add(as, SPL_OP_POPL,  0);
	    spl_asm_add(as, SPL_OP_HALT,  0);
	    spl_task_setcode(cb_task, spl_asm_dump(as));
	    spl_asm_destroy(as);
	    
	    spl_create(cb_task, cb_task->ctx, "callback", callback, SPL_CREATE_LOCAL);
	    spl_create(cb_task, cb_task->ctx, "arg1", arg1, SPL_CREATE_LOCAL);
	    spl_create(cb_task, cb_task->ctx, "arg2", arg2, SPL_CREATE_LOCAL);
	    
	    spl_clib_callback_run(cb_task);
	    
	    struct spl_node *retval = spl_get(spl_lookup(cb_task,
	                                cb_task->ctx, "retval", 0));
	    spl_task_destroy(cb_task->vm, cb_task);
	    
	    return retval;
	}
	
	void SPL_ABI(spl_mod_callback_init)(struct spl_vm *vm,
	                                struct spl_module *mod, int restore)
	{
	    spl_clib_reg(vm, "callback", handler_callback, 0);
	}


This example module (mod_callback.c) implements a function which should be
called with three arguments. The first one is a callback function and the other
two are arguments which are passed thru to the callback function. The return
value of the callback function is also the return value of the example
function. Example given:

	$ gcc -shared mod_callback.c -o mod_callback.so

	$ splrun -q 'load "callback";
	             debug callback(function(a,b) { return a+b; }, 23, 42);'
	SPL Debug: 65

As can be seen, it is not possible to call the callback function directly.
Instead one needs to create a seperate task (using spl_clib_callback_task())
and generate a little helper codepage which then calls the callback
function. This codepage can then be executed using the spl_clib_callback_run()
function.

In the example program I have used temporary SPL variables in the local
context of the task created by spl_clib_callback_task() to pass the callback,
the arguments and the return value to and from the callback codepage. A task
context is not very different from any other SPL node. So the variables can
be easily created with spl_create() and looked up with spl_lookup().

The SPL assembler code used in the example above is pretty generic and is
sufficient for the most cases where an SPL callback should be called from
C code. Simply add as many SPL_OP_PUSHA records as you have arguments.

The command 'splrun -AN' can be used to create SPL assembler code from SPL
scripts. So that command can be used if the above example does not cover
your requirements. For example the code used in the above example can be
generated like this:

	$ splrun -AN -q 'var retval=callback(arg1, arg2);'
	# SPL Assembler Dump
	:16     PUSHC       "retval"         # (1 byte arg) 10
	:18     ZERO
	:19     PUSHA       "arg2"           # (1 byte arg) 0
	:21     PUSHA       "arg1"           # (1 byte arg) 5
	:23     DCALL       "callback"       # (1 byte arg) 17
	:25     POPL
	:26     HALT
	# Total size: 37 bytes (11 text, 26 data).

You could also call the SPL compiler directly in the C function to generate
the SPL assembler code. But the SPL compiler is rather slow and calling it for
such a small code snippet is an unnecessary overhead.

