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)
     


      



  
  

  

  

