Sizzle is an interpreter for a programming language. It is designed to be used as an embedded extension language and as a scripting language for a wide variety of purposes.
This manual documents how to master the embedding process needed to make use of Sizzle's features in a C application. For more information on Sizzle, on Scheme programming and a reference of the variables and procedures available in Sizzle see section `Introduction' in The Sizzle Reference Manual.
Chapter 2 deals with embedding, containing a tutorial which shows how to use Sizzle to parse the initialization file for a little example program.
In Chapter 3, the C API for embedding is documented.
The following Chapter 4 deals with mechanisms and concepts for extending the Sizzle interpreter for special purposes by adding data types and primitive procedures.
The Sizzle interpreter is implemented in the library `libsizzle'. This library is installed when Sizzle is installed and actually is used by the Sizzle interpreter `sizzle' for all its work. `libsizzle' can be embedded in other programs very easily, and this chapter will show how to do it.
When compiling against the Sizzle library, you have to tell the compiler
where to find the Sizzle include files, and the linker, where to find
the library itself. When you installed Sizzle in the standard location
(with $prefix set to `/usr/local/lib'), the files are likely
to be found, but embedding Sizzle should be possible when installed in
another location too. That is the reason the script
`sizzle-config' was included in the distribution. This script gets
installed in the same location as the `sizzle' interpreter, and can
be used to obtain information about the Sizzle installation.
`sizzle-config' understands three commands, which must be given on the command line. Either command causes the script to print out a line of options, suitable for inclusion in the compiler or linker command.
compile
-I compiler option for finding the include files.
link
-L/usr/local/lib -R/usr/local/lib -lsizzle -lm -ldl)
static-link
/usr/local/lib/libsizzle.a -lm -ldl)
One design goal for Sizzle was to make it easy to use the interpreter for parsing initialization files. Using a complete programming language for init files has the advantage that a lot intelligence for setting up a program can be put into these init files. One popular example is the Emacs editor. This chapter will explain in tutorial style which steps are necessary to use Sizzle for this purpose.
The four steps in the following sub-section describe in detail what to do if you want to include Sizzle into your application for the purpose of parsing init files. In the `examples' directory of the source distribution of Sizzle are included the files `example.c' and `startup.scm', which illustrate the following steps.
Before you can use the `libsizzle' library, you have to initialize
it. Two steps are necessary to do that. First, you have to tell it the
address of the top of the C stack. This is done by declaring a local
dummy variable and passing its address to the function
zzz_set_top_of_stack().
int
main (int argc, char * argv[])
{
zzz_scm_t dummy;
/* Announce top of stack to Sizzle library, so conservative marking
works. */
zzz_set_top_of_stack (&dummy);
Then the library initialization is performed by a call to
zzz_initialize().
/* Initialize the library. */ zzz_initialize ();
The last step is not mandatory, but very useful. You can pass the command line options given to your program to the interpreter to make it available to your Scheme code.
/* Make command line options available to Scheme code. The zero'th
argument is handled specially to make pre-processing of the
arguments easier (see src/sizzle.c for details). */
zzz_set_arguments (argc - 1, argv[0], argv + 1);
When the library is correctly initialized, you can bind variables in your C code to Scheme variables. Thus you can directly modify variables in your C program from Scheme code.
Three different data types are currently supported: integers, strings and boolean values. Whenever you bind the address of a C variable to a Scheme variable you have to tell the interpreter of which data type the variable is. The interpreter will then type-check assignments and signal errors whenever a value of a wrong type is stored into a variable. Additionaly, it is considered an error to store a string which is too long into a string variable.
The example program declares five variables of different types.
/* Bind some variables of different types. The last argument is a
flag whether the variable is allowed to get changed by Scheme
code. */
zzz_bind_int_variable ("binary-port", &binary_port, 0);
zzz_bind_int_variable ("command-port", &command_port, 0);
zzz_bind_bool_variable ("verbose", &verbose, 0);
zzz_bind_string_variable ("hostname", hostname, sizeof (hostname), 0);
zzz_bind_string_variable ("download-area", download_area,
sizeof (download_area), 0);
zzz_bind_scm_variable ("kill-lines", &kill_lines, 0);
The first argument in each of these calls is the name which will be given to the Scheme variable, the second argument is the address of the C variable and the last is a flag whether the variable should be read-only on the Scheme level. When declaring string variables, you have to pass the maximal length of the variable also, so range-checking can be performed.
A call to the function zzz_evaluate_file() finally loads the init
file, which may contain any valid Scheme code. The Scheme code has
access to all variables defined above. For details about the init file,
see the file `startup.scm' in the `examples' directory.
/* Evaluate startup file. */
if ((return_val = zzz_evaluate_file (zzz_toplevel_env, "startup.scm")) != 0)
fprintf (stderr, "example: error while reading init file\n");
The return value of zzz_evaluate_file() is zero when all went
right and a non-zero value otherwise.
The variable kill-lines was bound to a generic variable of type
zzz_scm_t. That means that any Scheme value can be stored in the
variable and that no type checking is performed as for the other
variables which have explicitly been bound to variable of type int,
boolean or string. When using such variables, you must be careful when
interpreting the values stored there. The following example shows how
to safely handle a variable which was supposed to hold a list of
strings.
/* Interpretation of bound variables of type zzz_scm_t must be done
before the library is finalized, otherwise dangling pointers will
make your life hard. */
/* Iterate over list. */
while (cons_p (kill_lines))
{
/* Make sure we are dealing with strings. */
if (string_p (car (kill_lines)))
{
printf ("Kill line: %s\n", string_val (car (kill_lines)));
}
kill_lines = cdr (kill_lines);
}
After parsing the init file, you will normally want to free all memory
used by the interpreter. This is done by a call to
zzz_finalize().
/* Free all memory used by the interpreter. */ zzz_finalize ();
Note that all Scheme values, even those which might be stored into C
variables using zzz_bind_scm_variable(), are no longer valid when
you have shut down the interpreter.
That's it! Not to hard, methinks.
This section documents the C API for embedding Sizzle into C applications.
The calling conventions of most API functions follow the same concept. The return value of those functions is an error code, values computed by the functions are normally returned in reference parameters. This is a little bit unconvenient when calling these functions, because a lot of temporary variables for holding the intermediate results must be declared, but provides a very powerful interface for error handling, since not only the fact that an error occured can be signalled, but also additional information about the error condition can be returned in the result variable.
That is the reason why those return codes and parameters are also used
for implementing exceptions. Whenever a call to an API function does
not return RESULT_SUCCESS, the error is immediately propagated up
the call chain. So not only errors are passed up, but also exceptions.
The exception objects passed up together with the exception code can be
examined and particular exceptions can be caught using this technique.
When using the Sizzle library, you habe the choice of using one of two
methods. See section Parsing Initialization files, where the first is shown
in detail. It consists of calling the functions
zzz_set_top_of_stack(), zzz_initialize() and
zzz_finalize(); the other method is used by the Sizzle
interpreter `sizzle' and uses only the call zzz_run(). The
former is more easily to incorporate into existing programs whereas the
latter is much simpler and less error-prone.
zzz_run() function.
zzz_run() does all
necessary initialization and finalization.
The function in this section all modify objects in the Scheme name space. They either create or modify Scheme variables, constants or functions, but they can also be used to query those objects.
All functions below create their bindings in the outermost scope, even below the toplevel environments. The bindings are thus equivalent in scope to the builtin variables and procedures (in fact, builtin bindings are created using these functions).
RESULT_SUCCESS if no error occurs, otherwise an error code is
returned and an error object is stored in the location pointed to by
result.
RESULT_SUCCESS is returned if no error occurs,
otherwise an error code is returned and an error object is stored in the
location pointed to by result.
zzz_define_constant() and
zzz_define_variable() is that the variables defined with the
former can not be modified by set! operations. Also, variables
defined using zzz_define_constant() are more efficient because
references to these constant variables are replaced by their values
during runtime.
Hash tables are represented as vectors which hold association lists. When storing a key/value pair, or looking up the value for a given pair, a hash value is calculated for the key and used as an index into the vector. Then the association list found at the index is scanned for the key.
zzz_hashq_ref uses eq? as the equality
predicate for searching for key, zzz_hashv_ref uses
eqv? and zzz_hash_ref uses equal?.
zzz_hashq_ref uses eq? as the equality
predicate for searching for key, zzz_hashv_ref uses
eqv? and zzz_hash_ref uses equal?.
Another method for interfacing Scheme and C code is declare variables in your C source and then bind the locations of these variables to Scheme variable names. The advantage is that you can then reference the values stored into the variables by simply using the variables like normal C variables. Variables created with these functions are totally transparent to the Scheme code, there is not even a method how the Scheme code can find out whether a given variable is bound to a C variable or not.
zzz_scm_t, but do not wish to bind that variable
to a Scheme variable using zzz_bind_scm_variable(). Returns
RESULT_SUCCESS on success and an error code otherwise.
Use one of these functions if you like to evaluate Scheme code. The functions which take an environment as a parameter evaluate the given expression, string or file in that environment, that means that all bindings which may be created are effectively created in the given environment. You can pass the variable zzz_toplevel_env for the env parameter to these functions if you do not have any special requirements.
exit. All
expressions in the file are evaluated in the environment env.
Evaluation is aborted as soon as an error is encountered or evaluation
is aborted using exit.
exit. All expressions in the string are
evaluated in the environment env. Evaluation is aborted as soon
as an error is encountered or evaluation is aborted using exit.
exit command with an exit code.
RESULT_SUCCESS is
returned, otherwise an error code and an error object is stored in
result.
RESULT_SUCCESS is returned, otherwise an error code and
an error object is stored in result.
When creating Scheme values, the functions defined in this section should be used. They guarantee correct interfacing to the memory management system and the garbage collector.
You should not use this function unless you are sure that the given
value fits into a fixnum. Use zzz_make_integer() instead.
strlen().
strlen(). The returned string is read-only and cannot be
modified with Scheme primitives like string-set!.
string-set!.
#f is returned, otherwise the canonic true Scheme object
#t is returned.
strlen() is used to
determine the string length. This function will return the same object
for all strings with the same length and the same character contents.
TAGGED_INFO_ARG_*_*_* constants for this
parameters.
When final is true, variable lookups will not descend the static chain further than to the created environment.
When read_only is true, variables defined in environments further down the static link can only be read, but not modified.
strlen() is
used to determine the string length. This function will return the same
object for all strings with the same length and the same character
contents.
'().
'(). The
returned vector is read-only and can not be modified using Scheme
primitives like vector-set!.
zzz_capture_continuation().
type_info_* constants) in tag, and additional information
in the parameters data0, data1 and data2. The format
of the data parameters depends on what the internal constructor function
for the tagged type expects.
syntax-rules. body.
TAG in the constructor name may be replaced by s8,
u8, s16, u16, s32, u32, s64,
u64, f32 or f64, depending on the needed datatype.
So there are actually ten of these constructor functions.
TAGvector-set!.
TAG in the constructor name may be replaced by s8,
u8, s16, u16, s32, u32, s64,
u64, f32 or f64, depending on the needed datatype.
So there are actually ten of these constructor functions.
zzz_copy_tree() instead.
not-available, and the
additional exception data args. This exception is thrown whenever
a primitive is called which is not supported under the currently running
version of Sizzle.
These type predicates are safe in the sense that you do not risk a
segmentation fault when applying any of them to an arbitraty value of
type zzz_scm_t. They simply return 0 if the condition they test
for is not satisfied. Use them before using any of the accessor
functions (see section Accessor functions).
Note that the return values of these predicates are boolean values in the C sense: zero means false and any other value means true.
null_p(c) returns a true value if c is the empty list.
fixnum_p(c) returns a true value if c is a fixnum object.
imm_p(c) returns a true value if c is an immediate value,
Immediate values are all values which are encoded into a pointer of type
zzz_scm_t, and which do not occupy any cells on the heap. The
empty list, all fixnum values and immediate form objects are immediate
values.
immediate_p(c) returns a true value if c is an immediate
form. Immediate forms are substituted for form applications to speed up
the evaluation of expressions.
cons_p(c) returns a true value if c is a cons pair.
integer_p(c) returns a true value if c is an integer
object. Both fixnum and long objects are integer objects.
procedure_p(c) returns a true value if c is a procedure
object. Procedure objects are primitive procedures, syntax forms and
lambda expressions.
number_p(c) returns a true value if c is a number
object. Fixnum, long and real objects are numbers.
tagged_p(c) returns a true value if c is a tagged
object. This macro is true for all values which are neither immediate in
the sense of immediate_p() nor cons cells in the sense of
cons_p().
string_p(c) returns returns a true value if c is a string object.
rostring_p returns returns a true value if c is a constant string
object.
bool_p(c) returns a true value if c is a boolean object.
char_p(c) returns a true value if c is a character object.
float_p(c) returns a true value if c is a real number
object.
symbol_p(c) returns a true value if c is a symbol.
func_p(c) returns a true value if c is a primitive
procedure.
form_p(c) returns a true value if c is a syntactic form.
lambda_p(c) returns a true value if c is a lambda closure.
error_p(c) returns true if c is an error object.
except_p(c) returns true if c is an exception object.
long_p(c) returns true if c is a long integer object.
env_p(c) returns true if c is an environment.
location_p(c) returns true if c is a location object.
rolocation_p(c) returns true if c is a constant location
object.
lloc_p(c) returns true if c is an lloc object.
gloc_p(c) returns true if c is a gloc object.
constant_p(c) returns true if c is a constant value object.
keyword_p(c) returns true if c is a keyword.
vector_p(c) returns a true value if c is a vector object.
rovector_p(c) returns a true value if c is a constant
vector object.
values_p(c) returns true if c is a multiple value object.
int_var_p(c) returns true if c is an integer variable
wrapper object, ro_int_var_p(c) returns true if c is a
read-only integer variable wrapper object.
bool_var_p(c) returns true if c is a boolean variable
wrapper object, ro_bool_var_p(c) returns true if c is a
read-only boolean variable wrapper object.
str_var_p(c) returns true if c is a string variable wrapper
object, ro_str_var_p(c) returns true if
c is a read-only string variable wrapper object.
regexp_p(c) returns true if c is a regular expression
object.
promise_p(c) returns true if c is a promise object.
macro_p(c) returns true if c is a macro code object.
syntax_p(c) returns true if c is a syntax object.
port_p(c) returns true if c is a port object.
fport_p(c) returns true if c is a standard IO port object.
fdport_p(c) returns true if c is a file desccriptor port
object.
sport_p(c) returns true if c is a string port object.
TAGvector_p(c) returns a true value if c is a
homogenous numeric vector object of the type indicated by TAG.
TAG in the predicate name may be replaced by s8, u8,
s16, u16, s32, u32, s64, u64,
f32 or f64, depending on the needed datatype. So there
are actually ten of these predicate macros.
ro_TAGvector_p(c) returns a true value if c is a
homogenous numeric vector object of the type indicated by TAG.
TAG in the predicate name may be replaced by s8, u8,
s16, u16, s32, u32, s64, u64,
f32 or f64, depending on the needed datatype. So there
are actually ten of these predicate macros.
The macros and functions in this section are used to query the properties of Scheme objects. Before applying them to any object, you have to check whether the object is of the correct type, because the accessor macros and functions will not check for validity.
tagged_info_* constants.
void * and must be casted before being used.
NULL if the
closure is anonymous.
zzz_scm_t.
regex_t, into
which the regular expression was compiled.
port_ungetc()
operation.
TAG in the constructor name may be replaced by s8,
u8, s16, u16, s32, u32, s64,
u64, f32 or f64, depending on the needed datatype.
So there are actually ten of these accessor macros.
TAG in the constructor name may be replaced by s8,
u8, s16, u16, s32, u32, s64,
u64, f32 or f64, depending on the needed datatype.
So there are actually ten of these accessor macros.
RESULT_SUCCESS on success or an error code and an error
object in the location pointed to by result if an error occurs.
RESULT_SUCCESS on success or an error code and an error object in
the location pointed to by result if an error occurs.
RESULT_SUCCESS on success or an error code and an error object in
the location pointed to by result if an error occurs.
RESULT_SUCCESS on success or an
error code and an error object in the location pointed to by
result if an error occurs.
RESULT_SUCCESS on success or an error code
and an error object in the location pointed to by result if an
error occurs.
RESULT_SUCCESS on
success or an error code and an error object in the location pointed to
by result if an error occurs.
fseek(). The new offset
after repositioning is returned in the location pointed to by
res_ofs. Returns RESULT_SUCCESS on success or an error
code and an error object in the location pointed to by result if
an error occurs.
RESULT_SUCCESS on success or an
error code and an error object in the location pointed to by
result if an error occurs.
RESULT_SUCCESS on
success or an error code and an error object in the location pointed to
by result if an error occurs.
RESULT_SUCCESS
on success or an error code and an error object in the location pointed
to by result if an error occurs.
zzz_define_tagged_type()
returns a type tag for the new type.
The following two functions can be given as arguments to
zzz_define_tagged_type(), if the data fields of the tagged type
contain variables of type zzz_scm_t.
zzz_scm_t.
The following functions do not seem to fit into any of the previous sections, but they may come in handy from time to time.
eq?. Returns 0 otherwise.
This macro can be undefined in the file `node.h', which will cause a function with the same functionality, but more error checking, to be compiled in.
eqv?. Returns 0 otherwise.
equal?. Returns 0 otherwise.
'() and a
circular list is a list which points to one of its own elements
somewhere.
In this section, we will see how Sizzle represents Scheme objects internally and how the memory needed for storing these objects is managed.
The Sizzle interpreter makes a difference between four types of Scheme objects: immediate values, fixnums, cons cells and tagged cells. These types are stored in memory differently, and we will look at the representation of them in detail in the following section.
Most functions use the opaque data type zzz_scm_t, which is
defined as a pointer to struct cell. A value of type
zzz_scm_t can represent four different kinds of data. They are
differentiated by their two least significant bits.
If these bits are zero, it is a pointer to a cons cell which in turn
holds two values of type zzz_scm_t. Cons cells are used to build
the normal Scheme lists from. Environments, procedures, hash tables
etc. are all build using lists, so objects of this type are quite
common.
Should the two least significant bits be 1, it represents an 29-bit, two-complement integer value, a so-called fixnum; in order to get the real value, the value must be right-shifted by three bits.
The third kind, encoded by a 2 in the two least significant bits is a so-called tagged cell where the first word of the object pointed to is the a combination of the tagged data type (a 16 bit unsigned int) and additional 13 information bits. The usage of the info bits depends on the particular object type. Function objects, for example, encode the expected parameter formet in these bits; and read-only strings and vectors have a bit set in the info field. The next three words of the tagged cell object (as you can calculate, a tagged object is at least 4 words long) are data words and the values stored there depend on the data type. Some types store a pointer to additional storage there, others use the three words to hold values larger than one word (floating point values, for example, store their 2-word value in the last two words of the object).
A value of 3 in the two least significant bits of the car of a cell means that the cell represents a so-called immediate value. Immediate values do not point to any heap-allocated object, but stand for themselves. The immediate forms, into which most syntactic forms are transformed on evaluation, are examples of this type of object.
Bit two is used to implement a marking garbage collector. Cells which have been seen during the scan phase of collection have this bit set in the car.
Cons cells and tagged cells are allocated on two different heaps, because they have a different size.
General layout of a variable of type zzz_scm_t:
7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0
+---------------+---------------+---------------+---------+-+-+-+
| | | | | | | |
+---------------+---------------+---------------+---------+-+-+-+
| | |
garbage collection mark --+ | |
| |
node type ----+-+
0 = cons cell,
'(),
immediate code
1 = integer (shifted)
2 = tagged cell
3 = immediate value
Cons cell pointers have zeros in their least significant bits. They can
be used as pointers to structures of type struct cell without
further modification. Note: when dealing with values of this type while
garbage collection is running is dangerous, because you have to mask out
bit #2 before dereferencing the pointer.
7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0
+---------------+---------------+---------------+---------+-+-+-+
|x x x x x x x x|x x x x x x x x|x x x x x x x x|x x x x x|x|0|0|
+---------------+---------------+---------------+---------+-+-+-+
\______________________________ _________________________^___/
\/ |
32-bit pointer to value of type `struct cell' |
Might be set during gc -+
The null pointer (which is the representation of the empty list) is a word of only zero bits. Only during garbage collection the third bit may be set and must be masked off.
7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0
+---------------+---------------+---------------+---------+-+-+-+
|0 0 0 0 0 0 0 0|0 0 0 0 0 0 0 0|0 0 0 0 0 0 0 0|0 0 0 0 0|x|0|0|
+---------------+---------------+---------------+---------+-+-+-+
\______________________________ _________________________^___/
\/ |
32-bit pointer to value of type `struct cell' |
Might be set during gc -+
Integer values, aka fixnums, have the following layout. The value of any fixnum value can be obtained by arithmetically right-shifting the value by three bits.
7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0
+---------------+---------------+---------------+---------+-+-+-+
|x x x x x x x x|x x x x x x x x|x x x x x x x x|x x x x x|x|0|1|
+---------------+---------------+---------------+---------+-+-+-+
\___________________________ __________________________/ ^
\/ |
Two-complement 29-bit-value |
Might be set during gc -+
Tagged cells hold the value 2 in their two least significant bits and a pointer to a tagged cell structure can be obtained by masking the lower 4 bits off.
7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0
+---------------+---------------+---------------+---------+-+-+-+
|x x x x x x x x|x x x x x x x x|x x x x x x x x|x x x x 0|x|1|0|
+---------------+---------------+---------------+---------+-+-+-+
\______________________________ _______________________^_^___/
\/ | |
32-bit pointer to tagged object | |
| |
Zero, because tagged objects are aligned to 16 bytes + |
|
Might be set during gc -+
Immediate codes have their least significant two bits set and their value encoded in the upper 29 bits.
7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0
+---------------+---------------+---------------+---------+-+-+-+
|x x x x x x x x|x x x x x x x x|x x x x x x x x|x x x x x|x|1|1|
+---------------+---------------+---------------+---------+-+-+-+
\___________________________ __________________________/ ^
\/ |
any value, depending of the immediate value |
Might be set during gc -+
When looking at any cell on one of the heaps, the values defined in the following paragraphs are valid.
On the cons heap, any cell can be either free or in use.
Free cells carry a pointer to the next entry in the free list in the cdr and the value `2' in the car.
7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0
+---------------+---------------+---------------+---------+-+-+-+
|0 0 0 0 0 0 0 0|0 0 0 0 0 0 0 0|0 0 0 0 0 0 0 0|0 0 0 0 0|0|1|0|
+---------------+---------------+---------------+---------+-+-+-+
|x x x x x x x x|x x x x x x x x|x x x x x x x x|x x x x x|x|x|x|
+---------------+---------------+---------------+---------+-+-+-+
\______________________________ _____________________________/
\/
Pointer to next free cell
Used cons cells can be in one of the following four states.
The car of a cons cell either contains a cons cell pointer (or NULL), a tagged cell pointer, a fixnum or an immediate value. During garbage collection, bit #2 of the car may be set.
Pointer to cons cell
______________________________/\____________________________
/ \
7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0
+---------------+---------------+---------------+---------+-+-+-+
|x x x x x x x x|x x x x x x x x|x x x x x x x x|x x x x x|x|0|0|
+---------------+---------------+---------------+---------+-+-+-+
|x x x x x x x x|x x x x x x x x|x x x x x x x x|x x x x x|0|x|x|
+---------------+---------------+---------------+---------+-+-+-+
\______________________________ _____________________________/
\/
Any possible value of type zzz_scm_t
Pointer to tagged cell
______________________________/\____________________________
/ \
7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0
+---------------+---------------+---------------+---------+-+-+-+
|x x x x x x x x|x x x x x x x x|x x x x x x x x|x x x x x|x|1|0|
+---------------+---------------+---------------+---------+-+-+-+
|x x x x x x x x|x x x x x x x x|x x x x x x x x|x x x x x|0|x|x|
+---------------+---------------+---------------+---------+-+-+-+
\______________________________ _____________________________/
\/
Any possible value of type zzz_scm_t
Integer value
_____________________________/\_______________________
/ \
7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0
+---------------+---------------+---------------+---------+-+-+-+
|x x x x x x x x|x x x x x x x x|x x x x x x x x|x x x x x|x|0|1|
+---------------+---------------+---------------+---------+-+-+-+
|x x x x x x x x|x x x x x x x x|x x x x x x x x|x x x x x|0|x|x|
+---------------+---------------+---------------+---------+-+-+-+
\______________________________ _____________________________/
\/
Any possible value of type zzz_scm_t
Immediate value
_____________________________/\_______________________
/ \
7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0
+---------------+---------------+---------------+---------+-+-+-+
|x x x x x x x x|x x x x x x x x|x x x x x x x x|x x x x x|x|1|1|
+---------------+---------------+---------------+---------+-+-+-+
|x x x x x x x x|x x x x x x x x|x x x x x x x x|x x x x x|0|x|x|
+---------------+---------------+---------------+---------+-+-+-+
\______________________________ _____________________________/
\/
Any possible value of type zzz_scm_t
The first word of a tagged cell always containes a type tag (and maybe information in the upper bits); the tag 0 stands for an unused cell. The following three words may hold any value, they are not interpreted by the memory manager.
Free tagged cell:
7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0
+---------------+---------------+---------------+---------+-+-+-+
|0 0 0 0 0 0 0 0|0 0 0 0 0 0 0 0|0 0 0 0 0 0 0 0|0 0 0 0 0|0|1|0|
+---------------+---------------+---------------+---------+-+-+-+
|x x x x x x x x|x x x x x x x x|x x x x x x x x|x x x x x|x|x|x|
.\______________________________ _____________________________/.
. \/ .
. Pointer to next free cell .
|x x x x x x x x|x x x x x x x x|x x x x x x x x|x x x x x|x|x|x|
+---------------+---------------+---------------+---------+-+-+-+
|x x x x x x x x|x x x x x x x x|x x x x x x x x|x x x x x|x|x|x|
+---------------+---------------+---------------+---------+-+-+-+
\______________________________ _____________________________/
\/
Garbage
A use tagged cell looks like this. Note that the third bit of the first word may be set during garbage collection, just like for cons cells.
Type information Type tag
___________/\__________ _____________/\_____________
/ \ / \
7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0
+---------------+---------------+---------------+---------+-+-+-+
|x x x x x x x x|x x x x x x x x|x x x x x x x x|x x x x x|x|0|0|
+---------------+---------------+---------------+---------+-+-+-+
|x x x x x x x x|x x x x x x x x|x x x x x x x x|x x x x x|x|x|x|
+---------------+---------------+---------------+---------+-+-+-+
|x x x x x x x x|x x x x x x x x|x x x x x x x x|x x x x x|x|x|x|
+---------------+---------------+---------------+---------+-+-+-+
|x x x x x x x x|x x x x x x x x|x x x x x x x x|x x x x x|x|x|x|
+---------------+---------------+---------------+---------+-+-+-+
\______________________________ _____________________________/
\/
Pointer(s) to cell data / immediate data
In order to free the programmer from the burden of memory management, the Scheme runtime environment must reclaim storage when it is no longer accessible by the running program. This process is called Garbage Collection, or GC for short.
Memory allocation in Sizzle works as follows: functions creating data
objects call either the function zzz_alloc_tagged_cell() or
zzz_cons() (which in turn calls zzz_alloc_cons_cell()).
After a certain number of calls to one of these functions, the garbage
collection is invoked. When not enough free memory is reserved for
fulfilling the allocation request, more memory is requested from the
operating system and the space needed is reserved. Note that Sizzle
does not invoke the garbage collector when the freelists are empty,
because it tries to avoid garbage collection when memory is nearly full.
The garbage collector uses a simple mark--and--sweep algorithm. That means, that garbage collection starts by marking all directly and indirectly reachable objects as referenced. In the second step, all objects which are not marked are returned to the freelists of their respective types.
For marking all referenced objects it is necessary to find the root pointers through which all used objects can be reached. Sizzle performs conservative marking, that is, it marks all data objects which are pointed to by values on the stack of the C program it runs in and by marking all objects in locations which have been explicitly made known to the interpreter. This mechanism requires that the interpreter knows how to find the beginning and the end of the stack.
Two methods are provided to make these addresses available: The first is to make the embedding application calling a library function which calls back to the procedure doing the applications work. With this method, the library can calculate the end of the stack automatically, but it takes over the main control function of the program. The Sizzle command line interpreter uses this method.
The other method is to make the client code call explicitly a library function and tell it where the end of the stack is by passing the address of a local variable. The tutorial in this manual is an example for this procedure (see section Parsing Initialization files).
The marking phase is performed by traversing all objects pointed by the root pointers. Immediate objects do not need to get marked, because they do not use heap memory at all. What happens to other objects, depends on their type. Cons cells are directly known to the mark function, and the funtion tries to reduce the needed stack space by avoiding deep recursion. Therefore, the spine of lists is marked in a loop, marking recursively only the list elements. Because lists in Scheme are normally long and shallow, this speeds up marking a lot.
Tagged objects are marked by calling the mark function for the type of the object. The mark function is responsible for calling the mark function recursively for all Scheme objects referenced by the object.
Sizzle has been designed in a way that it is easy to add additional data types. For now, this section only crudely documents the method for defining data types, so for details, please refer to the source code to find out how to do it. Start by reading the files `node.h' and `node.c' where all builtin data types are defined.
The internal representation of all data is documented in section Cell Representation.
The API functions for handling tagged types are documented briefly in section Defining Scheme Types.
New data types in Sizzle are introduced by defining so-called tagged types. Objects of tagged types occupy at least one tagged cell on the heap, but they may allocate additional memory and store pointers to those allocated areas into the heap cells. For every tagged type, a bunch of function must be defined which tell the memory manager to handle objects of these types without actually knowing the memory layout of the types.
This section documents what type functions are, and how to write them. First, I will summarize what functions are required, then I will describe each function type in detail, giving examples as I go.
The following type functions are defined:
display or write, and
is responsible for printing a textual representation of the object to a
port.
equal? to determine whether two
objects of the same type have the same structure.
The constructor function is responsible for initializing a tagged object. When it is called, a tagged cell is already allocated on the heap and has been initialized with the tagged type tag for the object. The constructor has three formal arguments: The address of the allocated cell, and three void pointer, which are used to pass arguments to the constructor function. The actual type and value of these arguments depend on the object type the cosntructor function was defined for.
The prototype for constructor functions looks like this:
typedef void * (* constructor_func_t) (zzz_tagged_t tagged, void * data0,
void * data1, void * data2);
Often, types only need to store their three pointer arguments in the
three data slots of a tagged cell. These types can use the library
function zzz_simple_constructor(), which does this cell
initialization. User types are free to use this pre-defined constructor
functions for their types, if its specification suits their needs.
The constructor function for the string datatype is given below, to serve as a non-trivial example.
static void *
string_constructor (zzz_tagged_t tagged, void * data0,
void * data1, void * data2)
{
char * p = (char *) data0;
int len = (int) ((long) data1);
void * ret;
ret = zzz_malloc (len + 1); /* +1 for terminating '\0'. */
if (p)
memmove ((char *) ret, p, len);
((char *) ret)[len] = '\0';
tagged->fill[1] = (unsigned long) ret;
tagged->fill[2] = (unsigned long) len;
return tagged;
}
Print functions must print a textual representation of an object to a Scheme port. They must match the following protoype:
typedef void (* print_func_t) (zzz_scm_t port, zzz_tagged_t tagged,
zzz_print_state_t state);
The argument port specifies the Scheme port to print to,
tagged is the object, and state specifies the current state
for printing. zzz_print_state_t is defined as a pointer to the
following structure, which currently includes only one field. This
field is a bitset for which the PRINT_* macros have been defined.
struct zzz_print_state
{
int flags;
};
#define PRINT_NEWLINE 0x01
#define PRINT_QUOTE 0x02
#define PRINT_ESCAPE 0x04
PRINT_NEWLINE is currently unused. PRINT_QUOTE means,
that the print function should do all necessary quoting for making the
textual representation suitable for reading the object back in using
read. PRINT_ESCAPE means that special characters must be
escaped in a way compatible with read. write for example,
calls the print functions with both PRINT_QUOTE and
PRINT_ESCAPE set, display calls the function without
these flags.
The print function for strings is rather complicated, but because it interprets the print flags, it makes a good example.
static void
string_print (zzz_scm_t port, zzz_tagged_t tagged, zzz_print_state_t state)
{
zzz_scm_t result;
char * p = tagged_data0 (tagged);
int len = (long) tagged_data1 (tagged);
assert (len >= 0);
if (!(state->flags & (PRINT_ESCAPE | PRINT_QUOTE)))
zzz_port_puts (port, p, len, &result); /* Fast path for `display'. */
else
{
if (state->flags & PRINT_QUOTE)
zzz_port_putc ('"', port, &result);
while (len-- > 0)
{
if ((state->flags & PRINT_ESCAPE) && (*p == '\\' || *p == '"'))
zzz_port_putc ('\\', port, &result);
zzz_port_putc (*p, port, &result);
p++;
}
if (state->flags & PRINT_QUOTE)
zzz_port_putc ('"', port, &result);
}
}
The free function must free all memory which might have been allocated in the constructor. As an example, here is the string type free function.
static void
string_free (zzz_tagged_t tagged)
{
int len = (long) tagged_data1 (tagged);
zzz_free (tagged_data0 (tagged), len + 1); /* + 1 for terminating '\0' */
}
The equal function must determine whether two objects of the same type
have the same structure in the sense of equal?. For strings,
that means that the strings must be tested whether they have the same
length and the same character contents.
static int
string_equal (zzz_tagged_t tagged0, zzz_tagged_t tagged1)
{
char * p1 = tagged_data0 (tagged0);
char * p2 = tagged_data0 (tagged1);
int len1 = (long) tagged_data1 (tagged0);
int len2 = (long) tagged_data1 (tagged1);
if (len1 != len2)
return 0;
if (len1 == 0)
return 1;
len1 = memcmp (p1, p2, len1);
if (len1)
return 0;
else
return 1;
}
The mark function for strings is empty, because no other Scheme objects are referenced. It would be possible to pass a null pointer to the type definition function also, if no mark function is required.
static void
string_mark (zzz_tagged_t tagged)
{
}
Here is a second example, the mark function for vector objects. It calls the library mark function for every vector element.
static int
vector_equal (zzz_tagged_t tagged0, zzz_tagged_t tagged1)
{
zzz_scm_t * vec1 = vector_val (tagged0);
unsigned long len1 = vector_len (tagged0);
zzz_scm_t * vec2 = vector_val (tagged1);
unsigned long len2 = vector_len (tagged1);
unsigned long x;
if (len1 != len2)
return 0;
for (x = 0; x < len1; x++)
{
if (!zzz_equal (vec1[x], vec2[x]))
return 0;
}
return 1;
}
Like for the constructor function, a library function exists to be used
as a mark function when your tagged type has a certain structure.
Whenever a tagged type is to be created which should hold Scheme values
in the data slots of the tagged cells, you can use the function
zzz_simple_mark(). This function simply calls the system's mark
function for all three data slots.
After writing the type functions, you have to register the new type with
the library. This is done by calling the type definition function
zzz_define_tagged_type(), which has this protoype:
unsigned long zzz_define_tagged_type
(constructor_func_t constructor_func,
print_func_t print_func,
free_func_t free_func,
mark_func_t mark_func,
equal_func_t equal_func);
The return value of this function is the type tagged, which can then be
used to create objects of the given type. It is used in calls to the
tagged typ creation function zzz_create_tagged_type(). It is
called with the type tag obtained by the call to the register function
and three opaque pointers, which will be passed to the type's
constructor function.
zzz_scm_t zzz_create_tagged_cell (unsigned long tag, void * data0,
void * data1, void * data2);
New port types can be created on the C as well as on the Scheme level. For now, refer to the files `sport.c' and `sport.h', which define string ports and which will be instructive if you want to build your own port types in C. The other port types are implemented in the files `fport.[ch]', `fdport.[ch]' and `scmport.[ch]'.
On the Scheme level, the procedure make-soft-port can be used to
create ports which handle in- and output in a special way. Refer to the
Reference Manual for more information. Scheme ports are implemented in
the files `scmport.c' and `scmport.h'.
It is easy to add primitive procedures to the interpreter. The process involves two steps. You have to write a C function which handles the primitive procedure calls and you must tell the interpreter under which name your function wants to be called.
This is a small example for writing a C primitive. The code is actually
taken from the code of the Sizzle library, it implements the primitive
function eval.
/*:doc
eval
(eval expr [environment]) => value(s)
Evaluate the expression expr in the environment specified by
environment and return the resulting value(s). environment
defaults to the current top-level environment.
doc:*/
#define FUNC_NAME "eval"
static result_t
eval_func (zzz_scm_t env, zzz_scm_t form, zzz_scm_t new_env,
zzz_scm_t * result)
{
static char * s_func_name = FUNC_NAME;
zzz_scm_t res;
if (zzz_eq (new_env, zzz_undefined))
new_env = zzz_current_environment;
else
CHECK_TYPE (2, env_p (new_env), zzz_environment_type_name);
return zzz_evaluate (new_env, form, result);
}
#undef FUNC_NAME
The first thing to notice is the definition of the CPP macro
FUNC_NAME. This macro must always be defined to be the name of
the Scheme primitive this function stands for, because it is used in
error messages produced by several support macros. s_func_name
must be declared always for the same reason, and in exactly the same
way.
eval takes an optional second parameter. Therefore, the code
must check whether any value was given for that parameter, if not the
value of the parameter will be zzz_undefined. Depending on this
check, eval either provides a default value or checks the given
value's type. The macro CHECK_TYPE will produce an error message
and return from the function if it is not. The first argument to
CHECK_TYPE is the parameter number to include in error messages,
the second is an expression which must evaluate to true in order to
proceed, and the last parameter is a string telling which parameter type
was expected. When the test for the second argument fails,
CHECK_TYPE will generate an appropriate error message including
the error message from the last parameter and return immediately from
the current function.
When the parameters have been verified, the function
zzz_evaluate() is called to evaluate the first parameter. The
return value if that call is simply returned, an the result pointer is
passed to the evaluation function, which will store the result value in
the given location.
After writing the C function for a primitive, we have to announce to the
library what we have laborously achieved. This is done using the API
function zzz_define_function(), like in the following example.
zzz_define_function ("eval", eval_func, argv_1_1_0);
Here, "eval" is the name of the Schme primitive we want to bind
the function to, eval_func is the C function we have defined in
the previous section and argv_1_1_0 is a descriptor of the
argument format our function expects. There are several of those
argv_*_*_* macros defined in `node.h'. The first number
stands for the number of fixed arguments the procedure expects, the
second number for the number of optional arguments and the last number
is 1 if a rest parameter will be accepted, to which a list of all
remaining arguments will be bound; and 0 if no rest argument is
acceptable.
Included in the Sizzle distribution is the script `sizzle-gen-if'.
This script can be used to automatically generate glue code for C
functions from an interface specification. It is possible (in some
simple cases) to generate a primitive function for wrapping a C library
function without writing a single line of code. An example for this is
the procedure fnmatch, which was included into the Sizzle core by
specifying its interface, and the rest (primitive procedure code,
parameter checking, parameter unboxing, return value boxing, primitive
definition, constant definition) was handled by `sizzle-gen-if'.
This section documents the use of the script. For further information, you can of course always refer to the source code and the file `fnmatch.if' in the directory `libsizzle' of the distribution, which demonstrates the use.
`sizzle-gen-if' is used as follows.
$ sizzle-gen-if infile outfile
The script must be invoked with the input file as the first and the
primary output file as the second argument. It will copy the input file
to the output file and replace interface specification marked with a
$ character with the corresponfing generated glue code.
Additionally, a init file will be created, with the name of the
output file with and `.x' appended. This file will contain the
necessary initialization code for registering primitives, creating
constants and symbols and protecting global variables from garbage
collection. The output file can then be #include'd into any C
source file, and the init file should be #include'd into the init
function of that source file, so that initialization can take place for
the generated wrapper code.
Suppose you have an interface specification file called
`fnmatch.if', which specifies how the C function fnmatch
must be called, and which possibly defines some constants or symbols.
To generate the glue code and the initialization statements, you have to
call `sizzle-gen-if' with the name of the file and the name the output
file should have.
mgrabmue@tortoise (~/cvs/sizzle/scripts): sizzle-gen-if fnmatch.if fnmatch.c mgrabmue@tortoise (~/cvs/sizzle/scripts):
When no errors are found, `sizzle-gen-if' does not print any messages. You can have a look at the current directory to see which files have been created.
mgrabmue@tortoise (~/cvs/sizzle/scripts): ls -l fnmatch.* -rw-r--r-- 1 mgrabmue mgrabmue 3263 Aug 22 15:26 fnmatch.c -rw-r--r-- 1 mgrabmue mgrabmue 568 Aug 22 15:26 fnmatch.c.x -rw-r--r-- 1 mgrabmue mgrabmue 658 Aug 22 12:26 fnmatch.if mgrabmue@tortoise (~/cvs/sizzle/scripts):
As you can see, the output file `fnmatch.c' exists now, and it is considerably bigger than the input file. This can give you a feeling how much typing you have saved by using `sizzle-gen-if' :-)
Additionally, the initialization file `fnmatch.c.x' was created. It holds the code needed to register the newly generated primitive procedure, so that is gets accessible to the outer world.
An interface specification file is processed by `sizzle-gen-if' under the following rules:
$, which indicates the
beginning of an interface specification.
$. After this character, a Scheme
object is read using the standard procedure read and processed as
an interface specification. All whitespace following the specification
will be discarded. The result of applying the interface specification
will then be written to the output file.
A specification file can contain arbitrary text. The use of interface
specifications must be explicitly announced with the character
$. All other data is simply copied to the output file. This is
useful to include things like preprocessor statements, other variable
declarations or even helper functions in the interface file.
When a $ character is read, a Scheme expression representing an
interface specification is expected. After reading this expression
(which will discard any whitespace after the expression), character
copying starts again, until a $ is read, and so on.
This is the grammar for an interface specification.
<if-spec> ::= <func-spec> | <cpp-const-spec> |
<symbol-spec> | <exception-spec>
<func-spec> ::= "(" "function" <ret-type> <func-name>
"(" <param-list> ")" [glue-code] ")"
<func-name> ::= identifier
<ret-type> ::= <type> | "<void>"
<param-list> ::= <empty> | <param> <param-list>
<param> ::= "(" <param-name> <param-type> [<keyword> ...] ")"
<param-name> ::= identifier
<param-type> ::= <type>
<keyword> ::= "invalidate" | "unchecked" | "ignored" |
"optional" | "rest"
<type> ::= "<int>" | "<string>" | "<real>" | "<file>" |
"<pointer>" | "<object>"
<glue-code> ::= string
<cpp-const-spec> ::= "(" "cpp-constant" <c-name> <type> ")"
<scheme-name> ::= identifier
<symbol-spec> ::= "(" "symbol" <scheme-name> <c-name> ")"
<exception-spec> ::= "(" "exception" <scheme-name> <c-name> ")"
<c-name> ::= identifier
<func-name> must be the name of the C function to be wrapped. The
wrapper function will be called <func-name>_func.
<ret-type> is the expected return type of the function and may be
<void> if it is to be ignored. The return type <ret-type> is
used to construct the correct return value for the function's result.
The parameter list <param-list> specifies the names of the
parameters for the function and their types, so that type-checking code
can be emitted.
The option unchecked says that no type checking is performed for
this parameter, it will be passed through to the called function. Note
that this seldom makes sense, except for debug printing or for wrapping
a function aware of Sizzle's Scheme types.
The option ignored tells the processor not to emit type checking
code for this parameter, and that it will not be passed to the wrapped
function. It will be silently ignored.
The additional keyword invalidate, which can be passed as a third
element in a parameter specification, is currently only implemented for
pointer values (type <pointer>). It will cause the emission of
extra code which will invalidate the parameter pointer object with a
call to zzz_invalidate_pointer(). This is useful if the wrapped
function performed some action rendering the pointer in the pointer
object useless. An invalidated pointer cannot be passed to any wrapped
function expecting <pointer> arguments after that, because they
check a pointer object's valid flag. Thus pointer object handling gets
a little bit more safe.
When optional is passed as an option, then the passed parameter
is allowed to be undefined. You should either specify ignored
also, to avoid passing an undefined value to the wrapped function, or
you must give your own <glue-code> which handles that case
properly.
The option rest may be given on the last argument. When given,
it means that this argument will be the rest argument to which a list of
the remaining argument will be bound. The type of this variable is
always a list, so no parameter type checking code is emitted. The type
for this parameter should always be <object>.
The specifications for symbols and exception will create global variables called <c-name> and bind symbol objects with the contents <scheme-name> to them. Also, the variables will be protected from garbage collection.
The <cpp-const-spec> specification creates a Scheme constant which is bound to the value which gets substituted by the CPP macro <c-name>. The <type> argument is needed to create the constructor for the constant object.
To start with, I want to give a feeling of what `sizzle-gen-if' can do. At first, it may seem extremely powerful, but sooner or later you will find out that the C functions you want to wrap are not as well suited as in this (contrived) example.
Take this specification:
$(function <int> strlen ((string <string>)))
Here we declare a function
strlen.
<int>.
<string> with the name string.
When passed through `sizzle-gen-if', this specification is transformed to this C function.
/*:doc
strlen
(strlen string) => integer
Apply the C function strlen to the following parameters:
string: string
doc:*/
#define FUNC_NAME "strlen"
static result_t
strlen_func (zzz_scm_t env, zzz_scm_t string, zzz_scm_t * result)
{
static char * s_func_name = FUNC_NAME;
zzz_scm_t __retval;
CHECK_TYPE (1, string_p (string), zzz_string_type_name);
{
int res = strlen (string_val (string));
__retval = zzz_make_integer (res);
}
*result = __retval;
return RESULT_SUCCESS;
}
#undef FUNC_NAME
Note how the type of the actual parameter is checked for correctnes. Then the string value is extracted from the string object before calling the wrapped function, and the result of the function call is stored into an integer object before returning it.
The specification and will also generate an init file containing the
following code, which will register the primitive under the name
strlen as a function taking exactly one argument.
zzz_define_function ("strlen",
(zzz_prim_t) strlen_func,
argv_1_0_0);
The automatically generated wrapper functions are nice, but they are not
as flexible as often is needed. For example, it is not possible to
specify <number> as an argument type and have the script
automatically generate code which selects the value depending on the
actual type of the parameter, which may be integer or real. Therefor,
the additional parameter <glue-code> is provided for interface
specifications. When this code is present, the body of the wrapper
procedure will not be generated, but instead <glue-code> will be
inserted. The glue code must assign a return value to the variable
__retval, but the positive effect is that argument type checking
is performed, so the glue code can rely on the fact that the parameters
have the types specified in the interface specification.
Consider this specification for the primitive sin:
$(function <real> sin ((x <number>))
"
double d;
if (integer_p (x))
d = (double) integer_val (x);
else if (float_p (x))
d = float_val (x);
else
abort ();
__retval = zzz_make_float (sin (d));
")
The glue code tests which number type the parameter x has, and performs the correct action according to the type. When processed with `sizzle-gen-if', the resulting code looks as follows:
/*:doc
sin
(sin x) => real
Apply the C function sin to the following parameters:
x: <number>
doc:*/
#define FUNC_NAME "sin"
static result_t
sin_func (zzz_scm_t env, zzz_scm_t x, zzz_scm_t * result)
{
static char * s_func_name = FUNC_NAME;
zzz_scm_t __retval;
CHECK_TYPE (1, number_p (x), zzz_number_type_name);
{
double d;
if (integer_p (x))
d = (double) integer_val (x);
else if (float_p (x))
d = float_val (x);
else
abort ();
__retval = zzz_make_float (sin (d));
}
*result = __retval;
return RESULT_SUCCESS;
}
#undef FUNC_NAME
Additionally, the following initialization file will be created:
zzz_define_function ("sin",
(zzz_prim_t) sin_func,
argv_1_0_0);
A lot of C library functions take pointers as their arguments. It is not possible for `sizzle-gen-if' to understand the meaning of all the possible weird argument passing techniques C programmers have invented. Thus, `sizzle-gen-if' decides that all data types it cannot understand are opaque pointers. This does not catch all cases, but you already knew that `sizzle-gen-if''s capabilities were limited :-)
This is an example specification for the C library function free(). It demonstrates how to use the option keyword invalidate with pointer arguments.
$(function <void> free ((p <pointer> invalidate)))
expands to
/*:doc
free
(free p) => *unspecified*
Apply the C function free to the following parameters:
p: pointer
doc:*/
#define FUNC_NAME "free"
static result_t
free_func (zzz_scm_t env, zzz_scm_t p, zzz_scm_t * result)
{
static char * s_func_name = FUNC_NAME;
zzz_scm_t __retval;
CHECK_TYPE (1, valid_pointer_p (p), "pointer");
{
free (pointer_val (p));
__retval = zzz_unspecified;
zzz_invalidate_pointer (p);
}
*result = __retval;
return RESULT_SUCCESS;
}
#undef FUNC_NAME
When free_func() returns, the pointer p cannot be used with
wrapper functions anymore, because it has been invalidated. This is
what you want after you free a pointer, isn't it? Using this trick,
`sizzle-gen-if' can even make programming with pointers safer.
Jump to: b - c - e - f - g - i - k - l - m - n - p - r - s - t - v - z
This document was generated on 6 December 2000 using texi2html 1.56k.