This is object.txt in view mode; [Download] [Up]
This file presents some information about application defined types and
operator overloading. See ../demo/complex.sl for an explicit example of the
functions discussed here.
-------------------------------------------------------------------------------
Declaring a new type.
=====================
To create a new type and interface it with S-Lang, you have to do several
things.
1. You must write a routine to create the object. This routine will
then have to be made available as an intrinsic function for the
applications's script to create the object. For example, in the
complex.c demo, the function `complex' creates the object.
2. You have to create a routine that destroy the object. This routine
will be called automatically by S-Lang when it is necessary to delete
the object. In order for S-Lang to achieve this, you must register
this ``callback'' when you register the new type. The function
should be declared as taking no parameters and returning void, e.g.,
void destroy_type_callback (void);
3. Declare the new type to S-Lang. This is achieved by using the
function `SLang_register_class'. This function takes three
parameters and returns no-zero if the class (type) was sucessfully
added or zero if not (malloc failure).
This function has the prototype:
int SLang_register_class (unsigned char, VOID *, VOID *);
Here, VOID is a macro defined in slang.h. In may be either `void' or
`unsigned char' depending on the compiler (Old compilers do not
support void *).
The first parameter is an unsigned character in the range 128 to 255.
Integers outside this range (0 through 127) are reserved for S-Lang
itself. This integer (unsigned char) will serve as an identification
number for the type. For example, in complex.c, a macro is used:
#define COMPLEX_NUMBER 128
If more than one type is created, two different numbers will have to
be used (e.g., 128 and 129). As a result, an application may define
at most 128 different types.
The second parameter represents the function that is to be called to
destroy a variable of the new type. This function is described in
(2) above. It must be typecast to `VOID *'. If your type does not
need to be destroyed (it did not malloc anything and there is no
cleanup necessary), NULL may be used.
The third parameter represents the function that will be called to
obtain a printable representation of a variable of the new type.
NULL may be used if the function does not exist. See the discussion
below about this function.
Interacting with the new type
=============================
There are two fundamentally different ways of manipulating a new type: via
new intrinsic functions and by operator overloading. The latter method is
discussed in the next section.
In order for an intrinsic function to interact with an object it must
first access the object. S-Lang will not pass application defined objects
directly to the intrinsic function. It is up to the function to acquire
the object from the stack. This is easily accomplished using the
`SLang_pop_user_object' function call. It is prototyped:
SLuser_Object_Type *SLang_pop_user_object (unsigned char);
It takes a single parameter, the number which identifies the type, and
returns a pointer to the object. If the requested object it not at the
top of the stack, NULL be be returned and SLang_Error will be set
appropriately. Note that this function does not return a pointer to the
object; rather, it returns a pointer to a S-Lang defined structure that
serves as a wrapper around the object. The actual object may be obtained
by accessing the `obj' member of this structure (See below).
Because the object was popped from the stack, it is up to
the routine that popped it to handle memory management for the object.
This is facilitated by the function `SLang_free_user_object' which is
prototyped:
void SLang_free_user_object (SLuser_Object_Type *);
This function should be called when the intrinsic function is finished
with the object.
For example, consider the function `real_part' complex.c demo:
static double real_part (void)
{
SLuser_Object_Type *u;
Complex_Type *z;
double x;
if (NULL == (u = SLang_pop_user_object (COMPLEX_NUMBER))) return 0.0;
z = (Complex_Type *) (u->obj);
x = z->real_part;
SLang_free_user_object (u);
return x;
}
It contains all of the elements of the above discussion:
1. It uses `SLang_pop_user_object' to access the object that
represents the contains complex number. The actual structure that
represents the complex number is obtained from the `obj' member of
the structure returned from the function call.
2. It calls `SLang_free_user_object' when it is finished using the
object.
Operator Overloading
====================
Sometimes, but not always, it is desirable to extend the arithemetic
binary and unary operators `+', `-', etc... as well as the binary
comparison operators `==', `<', etc... to the new type. This is
definitely the case with the complex number type. The complex number type
is even more complicated because one also wants an object of that type to
interact in binary operations not only with itself but with integer and
floating point numbers.
The basic way to accomplish operator overloading is to create a function
that computes the value of the binary or unary operation and then declare
this function to S-Lang. The S-Lang functions used for declaring the
binary and unary functions are prototyped:
int SLang_add_unary_op (unsigned char, VOID *);
int SLang_add_binary_op (unsigned char, unsigned char, VOID *);
They return 1 upon success or zero upon failure. The third parameter is a
pointer to the appropriate function cast to a pointer to VOID. In the
case of `SLang_add_unary_op' the first parameter represents the type for
which the unary operators are to be defined for. The other function
requires two such types because a binary operation acts on two objects
which may or may not be of the same type. For example, complex.c defines
a function `complex_binary' to manipulate binary operations with the
complex number:
if (!SLang_add_binary_op (COMPLEX_NUMBER, COMPLEX_NUMBER,
(VOID *) complex_binary)
||!SLang_add_binary_op (COMPLEX_NUMBER, FLOAT_TYPE,
(VOID *) complex_binary)
||!SLang_add_binary_op (COMPLEX_NUMBER, INT_TYPE,
(VOID *) complex_binary)) return 0;
Here binary operations with the complex number are defined between the
complex number and another complex number, an integer, or a float.
Note that the same function `complex_binary' has been used for all three
cases. Once does not have to use the same function--- three different
ones could be used.
The function used to perform binary functions must be prototype like:
int complex_binary (int op, unsigned char a_type, unsigned char b_type,
VOID *ap, VOID *bp);
That is, it must return an integer and take 5 parameters. The first
parameter indicates the particular binary operation to be performed and
the last 4 parameters determine the values of the objects that are to
participate in the binary operation. Specifically, the paramters `ap' and
`bp' are pointers to the actual objects and `a_type' and `b_type' indicate
the type of the objects represented by `ap' and `bp', respectively.
For example, if the binary operation represented by the `!=' operator is
to be preformed between two complex numbers, `op' will have the value
`SLANG_NE' and both `a_type' and `b_type' will have values of
`COMPLEX_NUMBER' (100) and `ap' and `bp' will be pointers to
`Complex_Type'.
However, if the same operation is to be performed between an integer and a
complex number as in `z != 1', then `b_type' will have the value INT_TYPE
and `bp' will be a pointer to integer.
Note that the function must be able to handle all binary operations
defined for it. This includes:
SLANG_PLUS (+)
SLANG_MINUS (-)
SLANG_TIMES (*)
SLANG_DIVIDE (/)
SLANG_EQ (==)
SLANG_NE (!=)
SLANG_GT (>)
SLANG_GE (>=)
SLANG_LT (<)
SLANG_LE (<=)
If the function handles the indicated binary operation, it should return
1; otherwise, the operation is not defined for the type and the function
should return zero. For example, `<', `>', `>=' and `<=' operations are
not defined for complex numbers so the function should return 0 if `op'
represents one of these numbers.
A similar discussion holds for unary operations. In this case, the
function has a simpler protoptype of the form:
int complex_unary (int op, unsigned char type, VOID *z)
and `op' can be any of the following values:
SLANG_CHS -x
SLANG_SQR sqr(x)
SLANG_MUL2 mul2(x)
SLANG_ABS abs(x)
SLANG_SIGN sign(x)
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.