The guile-fltk / Extreme Wave procedural database is built on top of scheme, in the sense that all arguments and return values of a PDB function are scheme objects. This lets Guile do the type checking for PDB function arguments regardless of the language it was written in. Guile is also responsible for allocating and garbage collecting the passed scheme objects. Guile is not used to actually evaluate any code, unless the registered procedure was written in scheme.
So to write a procedural database function in c you really do not need to know scheme. You just need to know how to convert scheme objects to c types, which is very simple and sufficiently fast.
The first step in adding a function to the procedural database is to write the function in terms of SCM arguments. Lets start with a simple example, and build up to more complex functions.
0: void hello_world()
1: {
2: printf ("Hello World\n");
3: }
First we note that this function has 0 arguments and 0 return values, so we can use the PDB_PROC_0_0 macro to set it up. I'll write the code and then explain what I am doing:
0: PDB_PROC_0_0
1: ( hello_world,
2: "hello-world",
3: "Prints \"Hello World\"." )
4: {
5: printf ("Hello World\n");
6: }
Line 0 uses the PDB_PROC_X_Y macro to set the function up (X is the number of arguments, and Y is the number of return values.)
Line 1 is the name of the c function.
Line 2 is the name of the procedural database function.
Line 3 is the "docstring". This should be a short sentence that describes the function.
Since the above example doesn't take any arguments or return any values, there's not much more to it, so lets move on to something a little more complex:
0: PDB_PROC_2_1
1: ( advanced_hello_world,
2: "advanced-hello-world",
3: "Prints hello to the specified world, N times. Return N+1.",
4: world, N,
5: PDBstr, PDBint,
6: N_1,
7: PDBint )
8: {
9: PDB_ASSERT_2 (advanced_hello_world, world, N);
10: char * world_c = gh_scm2newstr (world);
11: int N_c = gh_scm2int (N);
12: for (int i=0; i<N_c; i++)
13: printf("%s\n", world_c);
14: free (world_c);
15: return gh_int2scm(N_c + 1);
16: }
Line 0 specifies the PDB_PROC_2_1 macro (2 arguments, 1 return value).
Lines 1-3 serve the same purpose as before.
Line 4 is the list of arguments. In this case "world" and "N". All PDB function arguments are SCM objects. The PDB_PROC_X_Y macro generates a function that looks like this for the compiler:
SCM advanced_hello_world (SCM world, SCM N) {
Line 5 is a list of types for each SCM argument. In this example, "world" is of type PDBstr (a string), and "N" is of type PDBint (an integer).
Line 6 is a list of the return values, in this case "N_1".
Line 7 is a list of the return value types. N_1 is of the PDBint.
So in summary, the c/c++ environment all procedural database arguments and return values are SCM objects. SCM objects can be of different types, and it up to the programmer to do the type checking. But this is done easily enough with the PDB_ASSERT_X (function, arg0, .. argX) macro as seen in line 9.
Lines 10 - 11 demonstrate how we convert scheme objects to c / c++ types. Here is a list of the most common conversion functions provided by Guile:
Booleans int gh_scm2bool(SCM); PDB_BOOL SCM gh_bool2scm(SCM); Ints int gh_scm2int(SCM); PDB_INT SCM gh_int2scm(int); Long Ints long gh_scm2long(SCM); PDB_LONG SCM gh_long2scm(int); Doubles double gh_scm2double(SCM); PDB_DOUBLE SCM gh_double2scm(double); Floats float gh_scm2float(SCM); PDB_FLOAT SCM gh_float2scm(double);
Lines 12-13 do the actual work of this function.
Line 14 frees the space allocated for the string, world_c.
Line 15 adds one to N_c and returns the value as N_1.
Once you have written functions to be included in the
procedural database, you simply need to register the functions
with the procedural database:
0: void my_functions_init ()
1: {
2: PDB_NEW_PROCEDURE (hello_world);
3: PDB_NEW_PROCEDURE (advanced_hello_world);
4: }
Once my_functions_init() is called, "hello-world" and "advanced-hello-world" will be available from the procedural database.
The less-than-magical macros PDB_PROC_X_Y, PDB_ASSERT_X, and PDB_NEW_PROCEDURE are used extensively, and you may be wondering what they are doing. For the most part they exist simply to save you some typing, and hide the PDB_Info structure that each procedural database function must have. If macros offend you, the non-macro forms are perfectly acceptable and easy to understand. Here are the macro expansions for the curious:
PDB_PROC_X_Y
0: PDB_PROC_1_1
1: ( function,
2: "function-name",
3: "function documentation",
4: arg1,
5: PDBarg_type,
6: ret1,
7: PDBret_type )
expands to:
0: Pdb_Info function_i[] =
1: { {0, "function-name"},
2: {0, "function documentation"},
3: {PDBarg_type, arg1},
4: {0, 0},
5: {PDBret_type, ret1},
6: {0, 0}};
7:
8: SCM function (SCM arg1)
PDB_ASSERT_X_Y
0: PDB_ASSERT_1 (function, arg1)
expands to:
0: pdb_assert (function_i, arg1)
PDB_NEW_PROCEDURE
0: PDB_NEW_PROCEDURE (function)
expands to:
0: pdb_new_procedure (function_i, function)
Last modified: Thu Jul 8 13:38:02 CDT 1999