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 interpreters data structures and adds some intinsic
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
unix system calls available use
if ((-1 == SLang_init_slang ()) /* basic interpreter functions */
|| (-1 == SLang_init_slmath ()) /* sin, cos, etc... */
|| (-1 == SLang_init_slunix ()) /* unix system calls */
|| (-1 == SLang_init_slfile ())) /* file I/O */
exit (EXIT_FAILURE);
See the \slang-run-time-library for more information about the
intrinsic functions.
There are several ways of calling the interpreter. The most common
method used by both jed and slrn is to use the
SLang_load_file
function to interprete a file. For example,
jed starts by loading a file called site.sl
:
if (-1 == SLang_load_file ("site.sl"))
SLang_restart (1);
The SLang_load_file
function returns zero upon if successful, or -1
upon failure. The SLang_restart
function resets the
interpreter back to its default state.
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\");"))
SLang_restart (1);
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);
}
Both 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.
Intrinsic functions are required to follow a few rules to cooperate with the interpreter.
Intrinsic function must take only pointer arguments. This is because when the interpreter calls an intrinsic function, it passes value 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.
Intrinsic functions can only return void
, int
,
double
, or char *
. A function such as
int *invalid (void);
is not permitted since it does not return one of these types. The
current implementation limits the number of arguments to 7
.
Another restriction is that the intrinsic 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 mechanisms for adding an intrinsic function to the
interpreter: SLadd_intrinsic_function
and
SLadd_intrin_fun_table
.
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 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_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 ?? 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 specifiy 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 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("min", 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, 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;
}
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("min", intrin_min_n, SLANG_DOUBLE_TYPE)
This function would be used in an interpreter script via a statement
such as
variable xmin = min (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 = min (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("min", 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 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,
unsigned char 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, its value is managed by the interpreter and
not the application. For this reason, it is recommended that such
variables be declared as read-only.
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.
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.