File: README.API

package info (click to toggle)
spl 1.0~pre6-4
  • links: PTS, VCS
  • area: main
  • in suites: jessie, jessie-kfreebsd
  • size: 2,364 kB
  • ctags: 2,070
  • sloc: ansic: 16,614; yacc: 3,182; sh: 299; makefile: 167; xml: 156
file content (833 lines) | stat: -rw-r--r-- 34,440 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833

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 primarily 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 functions 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 similar 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.


SPL and Multithreading
----------------------

SPL can be used in multithreaded environments using POSIX Threads. Be sure
that pthread support is enabled when running GNU make and that your program
links to the pthread library with -lpthread. Using the output of
spl-config --ldflags and spl-config --ldlibs should be sufficient if SPL
itself has been built with pthread support.

Only one SPL VM is allowed per thread. Multiple threads in one SPL script
is not possible. However, you can use the task module to allow for parallel
activities within one SPL VM. In this case each SPL function call will be
atomic and the SPL scheduler will switch between SPL tasks.This form of
parallel SPL tasks will look like a single thread or process to the
embedding C program or operating system.

Multiple truly threaded SPL VMs can be used by creating one SPL VM per
thread. In this case thread scheduling will be independent to SPL function
calls. This allows for increased responsiveness of the individual SPL VMs
in the case of heavy duty function calls in the SPL code.

Communication between threaded SPL VMs can be established with synchronized
registered C functions that perform some kind of communication, for example
event handling. If the communication path can be kept simple, such a
solution can be very efficient and still provide the benefits of a threaded
environment. In addition, utilizing pthread conditions and (timed) condition
wait calls provided by registered C functions can avoid excessive polling of
the communication path. See the man page for pthread_cond_timedwait(3)
for details.