The S-Lang library provides an interpreter that when embedded into an application, makes the application extensible. Examples of programs that embed the interpreter include the jed editor and the slrn newsreader.
Embedding the interpreter is easy. The hard part is to decide what application specific built-in or intrinsic functions should be provided by the application. The S-Lang library provides some pre-defined intrinsic functions, such as string processing functions, and simple file input-output routines. However, the basic philosophy behind the interpreter is that it is not a standalone program and it derives much of its power from the application that embeds it.
Only one function needs to be called to embed the S-Lang interpreter
into an application: SLang_init_slang
. This function
initializes the interpreter's data structures and adds some intrinsic
functions:
if (-1 == SLang_init_slang ())
exit (EXIT_FAILURE);
This function does not provide file input output intrinsic nor does
it provide mathematical functions. To make these as well as some
posix system calls available use
if ((-1 == SLang_init_slang ()) /* basic interpreter functions */
|| (-1 == SLang_init_slmath ()) /* sin, cos, etc... */
|| (-1 == SLang_init_array ()) /* sum, min, max, transpose... */
|| (-1 == SLang_init_stdio ()) /* stdio file I/O */
|| (-1 == SLang_init_ospath ()) /* path_concat, etc... */
|| (-1 == SLang_init_posix_dir ()) /* mkdir, stat, etc. */
|| (-1 == SLang_init_posix_process ()) /* getpid, umask, etc. */
|| (-1 == SLang_init_posix_io ()) /* open, close, read, ... */
|| (-1 == SLang_init_signal ()) /* signal, alarm, ... */
)
exit (EXIT_FAILURE);
If you intend to enable all intrinsic functions, then it is simpler
to initialize the interpreter via
if (-1 == SLang_init_all ())
exit (EXIT_FAILURE);
See the S-Lang Library Intrinsic Function Reference for more information about the
intrinsic functions.
There are several ways of calling the interpreter. The two most common method is to load a file containing S-Lang code, or to load a string.
The SLang_load_file
and SLns_load_file
functions may
be used to interpret a file. Both these functions return zero if
successful, or -1
upon failure. If either of these functions fail,
the interpreter will accept no more code unless the error state is
cleared. This is done by calling SLang_restart
function to
set the interpreter to its default state:
if (-1 == SLang_load_file ("site.sl"))
{
/* Clear the error and reset the interpreter */
SLang_restart (1);
}
When a file is loaded via SLang_load_file
, any non-public
variables and functions defined in the file will be placed into a
namespace that is local to the file itself. The
SLns_load_file
function may be used to load a file using a
specified namespace, e.g.,
if (-1 == SLns_load_file ("site.sl", "NS"))
{
SLang_restart (1);
SLang_set_error (0);
}
will load site.sl
into a namespace called NS
. If such a
namespace does not exist, then it will be created.
Both the SLang_load_file
and SLns_load_file
functions
search for files along an application-specified search path. This
path may be set using the SLpath_set_load_path
function, as
well as from interpeted code via the set_slang_load_path
function. By default, no search path is defined.
NOTE: It is highly recommended that an application embedding the
interpreter include the slsh lib directory in the search path. The
.sl
files that are part of slsh are both useful and
and should work with any application embedding the interpreter.
Moreover, if the application permits dynamically loaded modules,
then there are a growing number of excellent quality modules for
slsh that can be utilized by it. Applications that follow this
recommendation are said to be conforming.
Files are searched as follows: If the name begins with the
equivalent of "./"
or "../"
, then it is searched for
with respect to the current directory, and not along the load-path.
If no such file exists, then an error will be generated. Otherwise,
the file is searched for in each of the directories of the load-path
by concatenating the path element with the specified file name. The
first such file found to exist by this process will be loaded. If a
matching file still has not been found, and the file name lacks an
extension, then the path is searched with ".sl"
and
".slc"
appended to the filename. If two such files are found
(one ending with ".sl"
and the other with ".slc"
), then
the more recent of the two will be used. If no matching file has
been found by this process, then the search will cease and an error
generated.
The search path is a delimiter separated list of directories that
specify where the interpreter looks for files. By default, the
value of the delimiter is OS-dependent following the convention of
the underlying OS. For example, on Unix the delimiter is
represented by a colon, on DOS/Windows it is a semi-colon, and on
VMS it is a space. The SLpath_set_delimiter
and
SLpath_get_delimiter
may be used to set and query the
delimiter's value, respectively.
There are several other mechanisms for interacting with the
interpreter. For example, the SLang_load_string
function
loads a string into the interpreter and interprets it:
if (-1 == SLang_load_string ("message (\"hello\");"))
return;
Similarly, the SLns_load_string
function may be used to load
a string into a specified namespace.
Typically, an interactive application will load a file via
SLang_load_file
and then go into a loop that consists of
reading lines of input and sending them to the interpreter, e.g.,
while (EOF != fgets (buf, sizeof (buf), stdin))
{
if (-1 == SLang_load_string (buf))
{
SLang_restart (1);
}
}
Finally, some applications such as jed and slrn use another method of interacting with the interpreter. They read key sequences from the keyboard and map those key sequences to interpreter functions via the S-Lang keymap interface.
An intrinsic function is simply a function that is written in C and is made available to the interpreter as a built-in function. For this reason, the words `intrinsic' and `built-in' are often used interchangeably.
Applications are expected to add application specific functions to the interpreter. For example, jed adds nearly 300 editor-specific intrinsic functions. The application designer should think carefully about what intrinsic functions to add to the interpreter.
When implementing intrinsic functions, it is necessary to follow a few rules to cooperate with the interpreter.
The C version of an intrinsic function takes only pointer arguments. This is because when the interpreter calls an intrinsic function, it passes values to the function by reference and not by value. For example, intrinsic with the declarations:
int intrinsic_0 (void);
int intrinsic_1 (char *s);
void intrinsic_2 (char *s, int *i);
void intrinsic_3 (int *i, double *d, double *e);
are all valid. However,
int invalid_1 (char *s, int len);
is not valid since the len
parameter is not a pointer.
The return value of an intrinsic function must be one of the
following types: void
, char
, short
, int
,
long
, double
, char *
, as well as unsigned versions
of the integer types. A function such as
int *invalid (void);
is not permitted since int*
is not a valid return-type for an
intrinsic function. Any other type of value can be passed back to
the interpreter by explicitly pushing the object onto the
interpreter's stack via the appropriate "push" function.
The current implementation limits the number of arguments of an
intrinsic function to 7
. The "pop" functions can be used to
allow the function to take an arbitrary number as seen from an
interpreter script.
Another restriction is that the intrinsic function should regard all its parameters as pointers to constant objects and make no attempt to modify the value to which they point. For example,
void truncate (char *s)
{
s[0] = 0;
}
is illegal since the function modifies the string s
.
There are two basic mechanisms for adding an intrinsic function to the
interpreter: SLadd_intrinsic_function
and
SLadd_intrin_fun_table
. Functions may be added to a specified
namespace via SLns_add_intrinsic_function
and
SLns_add_intrin_fun_table
functions.
As an specific example, consider a function that will cause the
program to exit via the exit
C library function. It is not
possible to make this function an intrinsic because it does not meet
the specifications for an intrinsic function that were described
earlier. However, one can call exit
from a function that is
suitable, e.g.,
void intrin_exit (int *code)
{
exit (*code);
}
This function may be made available to the interpreter as an
intrinsic via the SLadd_intrinsic_function
routine:
if (-1 == SLadd_intrinsic_function ("exit", (FVOID_STAR) intrin_exit,
SLANG_VOID_TYPE, 1,
SLANG_INT_TYPE))
exit (EXIT_FAILURE);
This statement basically tells the interpreter that
intrin_exit
is a function that returns nothing and takes a
single argument: a pointer to an integer (SLANG_INT_TYPE
).
A user can call this function from within the interpreter
via
message ("Calling the exit function");
exit (0);
After printing a message, this will cause the intrin_exit
function to execute, which in turn calls exit
.
The most convenient mechanism for adding new intrinsic functions is
to create a table of SLang_Intrin_Fun_Type
objects and add the
table via the SLadd_intrin_fun_table
function. The table will
look like:
SLang_Intrin_Fun_Type My_Intrinsics [] =
{
/* table entries */
MAKE_INTRINSIC_N(...),
MAKE_INTRINSIC_N(...),
.
.
MAKE_INTRINSIC_N(...),
SLANG_END_INTRIN_FUN_TABLE
};
Construction of the table entries may be facilitated using a set of
MAKE_INTRINSIC
macros defined in slang.h
. The main
macro is called MAKE_INTRINSIC_N
and takes 11 arguments:
MAKE_INTRINSIC_N(name, funct-ptr, return-type, num-args,
arg-1-type, arg-2-type, ... arg-7-type)
Here name
is the name of the intrinsic function that the
interpreter is to give to the function. func-ptr
is a pointer
to the intrinsic function taking num-args
and returning
ret-type
. The final 7
arguments specify the argument
types. For example, the intrin_exit
intrinsic described above
may be added to the table using
MAKE_INTRINSIC_N("exit", intrin_exit, SLANG_VOID_TYPE, 1,
SLANG_INT_TYPE, 0,0,0,0,0,0)
While MAKE_INTRINSIC_N
is the main macro for constructing
table entries, slang.h
defines other macros that may prove
useful. In particular, an entry for the intrin_exit
function
may also be created using any of the following forms:
MAKE_INTRINSIC_1("exit", intrin_exit, SLANG_VOID_TYPE, SLANG_INT_TYPE)
MAKE_INTRINSIC_I("exit", intrin_exit, SLANG_VOID_TYPE)
See slang.h
for related macros. You are also encouraged to
look at, e.g., slang/src/slstd.c
for a more extensive examples.
The table may be added via the SLadd_intrin_fun_table
function, e.g.,
if (-1 == SLadd_intrin_fun_table (My_Intrinsics, NULL))
{
/* an error occurred */
}
Please note that there is no need to load a given table more than
once, and it is considered to be an error on the part of the
application it adds the same table multiple times. For performance
reasons, no checking is performed by the library to see if a table
has already been added.
Earlier it was mentioned that intrinsics may be added to a specified
namespace. To this end, one must first get a pointer to the
namespace via the SLns_create_namespace
function. The
following example illustrates how this function is used to add the
My_Intrinsics
table to a namespace called my
:
SLang_NameSpace_Type *ns = SLns_create_namespace ("my");
if (ns == NULL)
return -1;
return SLns_add_intrin_fun_table (ns, My_Intrinsics, "__MY__"));
The intrinsic functions described in the previous example were functions that took a fixed number of arguments. In this section we explore more complex intrinsics such as those that take a variable number of arguments.
Consider a function that takes two double precision numbers and returns the lesser:
double intrin_min (double *a, double *b)
{
if (*a < *b) return *a;
return *b;
}
This function may be added to a table of intrinsics using
MAKE_INTRINSIC_2("vmin", intrin_min, SLANG_DOUBLE_TYPE,
SLANG_DOUBLE_TYPE, SLANG_DOUBLE_TYPE)
It is useful to extend this function to take an arbitray number of
arguments and return the lesser. Consider the following variant:
double intrin_min_n (int *num_ptr)
{
double min_value, x;
unsigned int num = (unsigned int) *num_ptr;
if (-1 == SLang_pop_double (&min_value))
return 0.0;
num--;
while (num > 0)
{
num--;
if (-1 == SLang_pop_double (&x))
return 0.0;
if (x < min_value) min_value = x;
}
return min_value;
}
Here the number to compare is passed to the function and the actual
numbers are removed from the stack via the SLang_pop_double
function. A suitable table entry for it is
MAKE_INTRINSIC_I("vmin", intrin_min_n, SLANG_DOUBLE_TYPE)
This function would be used in an interpreter script via a statement
such as
variable xmin = vmin (x0, x1, x2, x3, x4, 5);
which computes the smallest of 5
values.
The problem with this intrinsic function is that the user must explicitly specify how many numbers to compare. It would be more convenient to simply use
variable xmin = vmin (x0, x1, x2, x3, x4);
An intrinsic function can query the value of the variable
SLang_Num_Function_Args
to obtain the necessary information:
double intrin_min (void)
{
double min_value, x;
unsigned int num = SLang_Num_Function_Args;
if (-1 == SLang_pop_double (&min_value, NULL, NULL))
return 0.0;
num--;
while (num > 0)
{
num--;
if (-1 == SLang_pop_double (&x, NULL, NULL))
return 0.0;
if (x < min_value) min_value = x;
}
return min_value;
}
This may be declared as an intrinsic using:
MAKE_INTRINSIC_0("vmin", intrin_min, SLANG_DOUBLE_TYPE)
It is possible to access an application's global variables from
within the interpreter. The current implementation supports the
access of variables of type int
, char *
, and
double
.
There are two basic methods of making an intrinsic variable
available to the interpreter. The most straight forward method is
to use the function SLadd_intrinsic_variable
:
int SLadd_intrinsic_variable (char *name, VOID_STAR addr,
SLtype data_type,
int read_only);
For example, suppose that I
is an integer variable, e.g.,
int I;
One can make it known to the interpreter as I_Variable
via a
statement such as
if (-1 == SLadd_intrinsic_variable ("I_Variable", &I,
SLANG_INT_TYPE, 0))
exit (EXIT_FAILURE);
Similarly, if S
is declared as
char *S;
then
if (-1 == SLadd_intrinsic_variable ("S_Variable", &S,
SLANG_STRING_TYPE, 1))
exit (EXIT_FAILURE);
makes S
available as a read-only variable with the name
S_Variable
. Note that if a pointer variable is made available
to the interpreter, it should be declared as being read-only to
prevent the interpreter from changing the pointer's value.
It is important to note that if S
were declared as an array of
characters, e.g.,
char S[256];
then it would not be possible to make it directly available to the
interpreter. However, one could create a pointer to it, i.e.,
char *S_Ptr = S;
and make S_Ptr
available as a read-only variable.
One should not make the mistake of trying to use the same address for different variables as the following example illustrates:
int do_not_try_this (void)
{
static char *names[3] = {"larry", "curly", "moe"};
unsigned int i;
for (i = 0; i < 3; i++)
{
int value;
if (-1 == SLadd_intrinsic_variable (names[i], (VOID_STAR) &value,
SLANG_INT_TYPE, 1))
return -1;
}
return 0;
}
Not only does this piece of code create intrinsic variables that use
the same address, it also uses the address of a local variable that
will go out of scope.
The most convenient method for adding many intrinsic variables to
the interpreter is to create an array of SLang_Intrin_Var_Type
objects and then add the array via SLadd_intrin_var_table
.
For example, the array
static SLang_Intrin_Var_Type Intrin_Vars [] =
{
MAKE_VARIABLE("I_Variable", &I, SLANG_INT_TYPE, 0),
MAKE_VARIABLE("S_Variable", &S_Ptr, SLANG_STRING_TYPE, 1),
SLANG_END_TABLE
};
may be added via
if (-1 == SLadd_intrin_var_table (Intrin_Vars, NULL))
exit (EXIT_FAILURE);
It should be rather obvious that the arguments to the
MAKE_VARIABLE
macro correspond to the parameters of the
SLadd_intrinsic_variable
function.
Finally, variables may be added to a specific namespace via the SLns_add_intrin_var_table and SLns_add_intrinsic_variable functions.
An aggregate data object is an object that can contain more than one data value. The S-Lang interpreter supports several such objects: arrays, structure, and associative arrays. In the following sections, information about interacting with these objects is given.
An intrinsic function may interact with an array in several different ways. For example, an intrinsic may create an array and return it. The basic functions for manipulating arrays include:
SLang_create_array
SLang_pop_array_of_type
SLang_push_array
SLang_free_array
SLang_get_array_element
SLang_set_array_element
The use of these functions will be illustrated via a few simple
examples.
The first example shows how to create an return an array of strings to the interpreter. In particular, the names of the four seasons of the year will be returned:
void months_of_the_year (void)
{
static char *seasons[4] =
{
"Spring", "Summer", "Autumn", "Winter"
};
SLang_Array_Type *at;
SLindex_Type i, four;
four = 4;
at = SLang_create_array (SLANG_STRING_TYPE, 0, NULL, &four, 1);
if (at == NULL)
return;
/* Now set the elements of the array */
for (i = 0; i < 4; i++)
{
if (-1 == SLang_set_array_element (at, &i, &seasons[i]))
{
SLang_free_array (at);
return;
}
}
(void) SLang_push_array (at, 0);
SLang_free_array (at);
}
This example illustrates several points:
First of all, the
SLang_create_array
function was used to create a 1 dimensional
array of 4 strings. Since this function could fail, its return value
was checked. Also SLindex_Type
was used for the array size and
index types. In S-Lang version 2, SLindex_Type
is typedefed to
be an int
. However, as this will change in a future version of
the library, SLindex_Type
should be used.
The SLang_set_array_element
function was
used to set the elements of the newly created array. Note that the
address containing the value of the array element was passed and not
the value of the array element itself. That is,
SLang_set_array_element (at, &i, seasons[i])
was not used. The return value from this function was also checked
because it too could also fail.
Finally, the array was pushed onto the interpreter's stack and then
it was freed. It is important to understand why it was freed. This
is because arrays are reference-counted. When the array was created,
it was returned with a reference count of 1
. When it was
pushed, the reference count was bumped up to 2
. Then since it
was nolonger needed by the function, SLang_free_array
was
called to decrement the reference count back to 1
. For
convenience, the second argument to SLang_push_array
determines whether or not it is to also free the array. So, instead
of the two function calls:
(void) SLang_push_array (at, 0);
SLang_free_array (at);
it is preferable to combine them as
(void) SLang_push_array (at, 1);
The second example returns a diagonal array of a specified size to the stack. A diagonal array is a 2-d array with all elements zero except for those along the diagonal, which have a value of one:
void make_diagonal_array (SLindex_Type n)
{
SLang_Array_Type *at;
SLindex_Type dims[2];
SLindex_Type i, one;
dims[0] = dims[1] = n;
at = SLang_create_array (SLANG_INT_TYPE, 0, NULL, dims, 2);
if (at == NULL)
return;
one = 1;
for (i = 0; i < n; i++)
{
dims[0] = dims[1] = i;
if (-1 == SLang_set_array_element (at, dims, &one))
{
SLang_free_array (at);
return;
}
}
(void) SLang_push_array (at, 1);
}
In this example, only the diagonal elements of the array were set.
This is bacause when the array was created, all its elements were
set to zero.
Now consider an example that acts upon an existing array. In particular, consider one that computes the trace of a 2-d matrix, i.e., the sum of the diagonal elements:
double compute_trace (void)
{
SLang_Array_Type *at;
double trace;
SLindex_Type dims[2];
if (-1 == SLang_pop_array_of_type (&at, SLANG_DOUBLE_TYPE))
return 0.0;
/* We want a 2-d square matrix. If the matrix is 1-d and has only one
element, then return that element. */
trace = 0.0;
if (((at->num_dims == 1) && (at->dims[0] == 1))
|| ((at->num_dims == 2) && (at->dims[0] == at->dims[1])))
{
double dtrace;
SLindex_Type n = at->dims[0];
for (i = 0; i < n; i++)
{
dims[0] = dims[1] = i;
(void) SLang_get_array_element (at, &dims, &dtrace);
trace += dtrace;
}
}
else SLang_verror (SL_TYPE_MISMATCH, "Expecting a square matrix");
SLang_free_array (at);
return trace;
}
In this example, SLang_pop_array_of_type
was used to pop an
array of doubles from the stack. This function will make implicit
typecasts in order to return an array of the requested type.
For the purposes of this section, we shall differentiate structures according to whether or not they correspond to an application defined C structure. Those that do are called intrinsic structures, and those do not are called S-Lang interpreter structures.
The following simple example shows one method that may be used to create and return a structure with a string and integer field to the interpreter's stack:
int push_struct_example (char *string_value, int int_value)
{
char *field_names[2];
SLtype field_types[2];
VOID_STAR field_values[2];
field_names[0] = "string_field";
field_types[0] = SLANG_STRING_TYPE;
field_values[0] = &string_value;
field_names[1] = "int_field";
field_types[1] = SLANG_INT_TYPE;
field_values[1] = &int_value;
if (-1 == SLstruct_create_struct (2, field_names,
field_types, field_values))
return -1;
return 0;
}
Here, SLstruct_create_struct
is used to push a
structure with the specified field names and values onto the
interpreter's stack.
A simpler mechanism exists provided that one has already defined a C structure with a description of how the structure is laid out. For example, consider a C structure defined by
typedef struct
{
char *s;
int i;
}
SI_Type;
Its layout may be specified via a table of
SLang_CStruct_Field_Type
entries:
SLang_CStruct_Field_Type SI_Type_Layout [] =
{
MAKE_CSTRUCT_FIELD(SI_Type, s, "string_field", SLANG_STRING_TYPE, 0),
MAKE_CSTRUCT_FIELD(SI_Type, i, "int_field", SLANG_INT_TYPE, 0),
SLANG_END_CSTRUCT_TABLE
};
Here, MAKE_CSTRUCT_FIELD is a macro taking 5 arguments:
MAKE_CSTRUCT_FIELD(C-structure-type,
C-field-name,
slang-field-name,
slang-data-type,
is-read-only)
The first argument is the structure type, the second is the name of
a field of the structure, the third is a string that specifies the
name of the corresponding field of the S-Lang structure, the fourth
argument specifies the field's type, and the last argument
specifies whether or not the field should be regarded as read-only.
Once the layout of the structure has been specified, pushing a S-Lang version of the structure is trival:
int push_struct_example (char *string_value, int int_value)
{
SI_Type si;
si.s = string_value;
si.i = int_value;
return SLang_push_cstruct ((VOID_STAR)&si, SI_Type_Layout);
}
This mechanism of structure creation also permits a S-Lang structure to be passed to an intrinsic function through the use of the SLang_pop_cstruct routine, e.g.,
void print_si_struct (void)
{
SI_Type si;
if (-1 == SLang_pop_cstruct ((VOID_STAR)&si, SI_Type_Layout))
return;
printf ("si.i=%d", si.i);
printf ("si.s=%s", si.s);
SLang_free_cstruct ((VOID_STAR)&si, SI_Type_Layout);
}
Assuming print_si_struct
exists as an intrinsic function,
the S-Lang code
variable s = struct {string_field, int_field};
s.string_field = "hello";
s.int_field = 20;
print_si_struct (s);
would result in the display of
si.i=20;
si.s=hello
Note that the SLang_free_cstruct
function was called after
the contents of si
were nolonger needed. This was necessary
because SLang_pop_cstruct
allocated memory to set the
char *s
field of si
. Calling
SLang_free_cstruct
frees up such memory.
Now consider the following:
typedef struct
{
pid_t pid;
gid_t group;
}
X_t;
How should the layout of this structure be defined? One might be
tempted to use:
SLang_CStruct_Field_Type X_t_Layout [] =
{
MAKE_CSTRUCT_FIELD(X_t, pid, "pid", SLANG_INT_TYPE, 0),
MAKE_CSTRUCT_FIELD(X_t, group, "group", SLANG_INT_TYPE, 0),
SLANG_END_CSTRUCT_TABLE
};
However, this assumes pid_t
and gid_t
have been
typedefed as ints. But what if gid_t
is a short
? In
such a case, using
MAKE_CSTRUCT_FIELD(X_t, group, "group", SLANG_SHORT_TYPE, 0),
would be the appropriate entry for the group
field. Of
course, one has no way of knowing how gid_t
is declared on
other systems. For this reason, it is preferable to use the
MAKE_CSTRUCT_INT_FIELD
macro in cases involving integer valued
fields, e.g.,
SLang_CStruct_Field_Type X_t_Layout [] =
{
MAKE_CSTRUCT_INT_FIELD(X_t, pid, "pid", 0),
MAKE_CSTRUCT_INT_FIELD(X_t, group, "group", 0),
SLANG_END_CSTRUCT_TABLE
};
Before leaving this section, it is important to mention that access to character array fields is not permitted via this interface. That is, a structure such as
typedef struct
{
char name[32];
}
Name_Type;
is not supported since char name[32]
is not a
SLANG_STRING_TYPE
object. Always keep in mind that a
SLANG_STRING_TYPE
object is a char *
.
Here we show how to make intrinsic structures available to the interpreter.
The simplest interface is to structure pointers and not to the actual structures themselves. The latter would require the interpreter to be involved with the creation and destruction of the structures. Dealing with the pointers themselves is far simpler.
As an example, consider an object such as
typedef struct _Window_Type
{
char *title;
int row;
int col;
int width;
int height;
} Window_Type;
which defines a window object with a title, size (width
,
height
), and location (row
, col
).
We can make variables of type Window_Type
available to the
interpreter via a table as follows:
static SLang_IStruct_Field_Type Window_Type_Field_Table [] =
{
MAKE_ISTRUCT_FIELD(Window_Type, title, "title", SLANG_STRING_TYPE, 1),
MAKE_ISTRUCT_FIELD(Window_Type, row, "row", SLANG_INT_TYPE, 0),
MAKE_ISTRUCT_FIELD(Window_Type, col, "col", SLANG_INT_TYPE, 0),
MAKE_ISTRUCT_FIELD(Window_Type, width, "width", SLANG_INT_TYPE, 0),
MAKE_ISTRUCT_FIELD(Window_Type, height, "height", SLANG_INT_TYPE, 0),
SLANG_END_ISTRUCT_TABLE
};
More precisely, this defines the layout of the Window_Type
structure.
Here, the title
has been declared as a read-only field. Using
MAKE_ISTRUCT_FIELD(Window_Type, title, "title", SLANG_STRING_TYPE, 0),
would allow read-write access.
Now suppose that My_Window
is a pointer to a Window_Type
object, i.e.,
Window_Type *My_Window;
We can make this variable available to the interpreter via the
SLadd_istruct_table
function:
if (-1 == SLadd_istruct_table (Window_Type_Field_Table,
(VOID_STAR) &My_Window,
"My_Win"))
exit (1);
This creates a S-Lang interpreter variable called My_Win
whose value
corresponds to the My_Win
structure. This would permit one to
access the fields of My_Window
via S-Lang statements such as
define set_width_and_height (w,h)
{
My_Win.width = w;
My_Win.height = h;
}
It is extremely important to understand that the interface described in
this section does not allow the interpreter to create new instances of
Window_Type
objects. The interface merely defines an association or
correspondence between an intrinsic structure pointer and a S-Lang
variable. For example, if the value of My_Window
is NULL
, then
My_Win
would also be NULL
.
One should be careful in allowing read/write access to character string
fields. If read/write access is allowed, then the application should
always use the SLang_create_slstring
and SLang_free_slstring
functions to set the character string field of the structure.
If your program that embeds the interpreter processes signals, then
it may be undesirable to allow access to all signals from the
interpreter. For example, if your program has a signal handler for
SIGHUP
then it is possible that an interpreter script could
specify a different signal handler, which may or may not be desirable.
If you do not want to allow the interpreter access to some signal,
then that signal can be made off-limits to the interpreter via the
SLsig_forbid_signal
function:
/* forbid a signal handler for SIGHUP */
SLsig_forbid_signal (SIGHUP, 1);
/* Allow a signal handler for SIGTERM */
SLsig_forbid_signal (SIGTERM, 0);
By default, all signals are allowed access from the interpreter.