3.21.0. Lush Interpreter Internals |
3.21.0.0. Introduction |
The TL/Open protocol provides tools for interfacing C code with Lush. These powerful tools allow you to call both your C functions and your C data structures like ordinary Lush functions and objects.
3.21.0.0.0. Prerequisites |
The Windows version of TL/Open (TL/Open for WinLush) requires the Microsoft Visual C++ 4.x compiler. Other compilers (e.g. Watcom, Borland, ...) may be supported in the future.
The Unix version of Lush requires a properly installed ANSI C compiler. We recommend using the freely available GNU GCC compiler on supported platforms. We also support the native ANSI C compiler of each platform when it comes with the operating system.
3.21.0.0.1. How to Read this Section of the Manual |
Chapter "Lush Internal Architecture" explains a lot of facts about the Lush internal mechanisms. This background helps understanding the odds of TL/Open. This information is necessary for accessing most Lush objects and implementing advanced extensions to the Lush system.
Chapter "TL/Open Examples" comments the examples provided with TL/Open. Three examples are provided with various levels of complexity.
Chapter "Installing TL/Open Extensions" explains where to install TL/Open extension in the Lush directories.
3.21.0.1. Interfacing a C Function to Lush |
There are two ways to interface a C function with Lush:
This manual focuses on the Dynamical Link method. Additional comments explain how the Static Link method diverges from the Dynamical Link method.
3.21.0.1.0. Preparations to Compile C Code |
3.21.0.1.0.0. Setting up a Compilation Project Workspace with Visual C++ (Windows). |
Troubleshooting: if you do not find the TLOpen wizard icon in the ``new project'' window, make sure that TL/Open and Visual C++ have been properly installed. You may run again the WinLush installation program and check the installation options.
The TL/Open wizard just displays a confirmation window describing the project type and locating the various directories containing include files and libraries. Visual C++ creates and open the project when you click button OK .
The new project contains just one file named "user_dll.c" . This skeleton file defines a simple function hypot which returns the square root of the sum of the squares of two numbers. is created and Click OK .
Pressing key F7 builds a dynamical library file (DLL) in the subdirectory "Debug" located under your compilation directory. This file is easy to locate because of the suffix ".DLL" .
You can debug your function by pressing key F5 . Visual C++ asks you the name of the debug executable. You must enter there the path of the WinLush main executable. This executable is located in the subdirectory "bin" of the WinLush installation directory. If you have installed WinLush in the default location, you must type "c:\program files\winlush\winlush\bin\winlush.exe" .
If you mistype this filename, Visual C++ will signal an error, but will no longer offer you to enter the debug executable path by just pressing key F5 . You will have to change this filename in the project settings by typing key Alt-F7 and selecting tab Debug .
WinLush will eventually start and Visual C++ will turn into debug mode. Type the following command in the WinLush console:
? (mod-load "<your-dir>/Debug/<your-project-name>.dll")
This command loads the dynamical library and declares the new function to the Lush interpreter. You can now type:
? (hypot 3 4) = 5
Using the Visual C++ debugger, you can set breakpoints in the C function "xhypot" and see the actual computations taking place in your TL/Open extension.
3.21.0.1.0.1. Setting up a Compilation Directory under Unix (or Cygwin). |
[ THIS PART NEED REWRITING ] STATIC LINK --> Use the source compilation tree, Install your extensions in file 'user.c' (equivalent to 'user_dll.c' for shared objects. DYNAMIC LINK --> Copy template from <"<prefix>/lush/open/unix">. Follow instructions below.
The template contains a a skeleton file named "user_dll.c" . This file shows the generic structure of the Lush extension files. This file defines a simple function hypot which returns the square root of the sum of the squares of two numbers.
You can start the compilation by:
% make
This generates a shared library named "module.so" (or module.sl on HP/UX, module.dll on Cygwin). You can now run "lush" and check that function hypot is not defined :
? hypot = ()
This can be changed by loading the shared object in the running instance of Lush:
? (mod-load "module")
Function hypot is now defined as a DX.
? hypot = ::DX:hypot ? (hypot 3 4) = 5
3.21.0.1.1. Writing a New Function |
3.21.0.1.1.0. user_dll.c File |
File "user_dll.c" is composed of three sections:
/* --- HEADER SECTION --- * This section loads the appropriate header files * (WIN32 details are specific to Visual C++ 4.x.) */ #include "tlopen.h"
/* --- PRIMITIVE SECTION --- * This section contains your new Lush primitives. * You may also choose to implement them in auxiliary files. */ DX(xhypot) { real x, y; ARG_NUMBER(2); ALL_ARGS_EVAL; x = AREAL(1); y = AREAL(2); return NEW_NUMBER(sqrt(x*x + y*y)); }
This function performs three tasks:
/* --- INITIALISATION SECTION --- * Function 'init_user_dll' is called when loading extension. * -- perform your initializations here * -- allocate and initialize global data structures. * -- declare your primitives here */ int init_user_dll(int major, int minor) { /* Check version */ if (major!=TLOPEN_MAJOR || minor<TLOPEN_MINOR) return -2; /* Perform initializations */ /* Declare primitives */ dx_define("hypot", xhypot); return 0; }
The first two lines test the version of the module against the version of the Lush interpreter. The macros TLOPEN_MAJOR and TLOPEN_MINOR represent the version number of the interpreter for which this module is compiled. The arguments major and minor represent the version number of the running interpreter. The running interpreter must have the same major version number and a greater or equal minor version number than the interpreter for which the module is compiled.
A return value of -2 means that the extension is incompatible with the current version of Lush. The interpreter then unloads the module and displays an appropriate error message.
A return value of -1 means that the initialization failed. The TL/Open extension is then unloaded and an error is reported. You should print an explanation because the error message only states that the module initialization failed.
This function must return 0 when the initialization succeeds.
3.21.0.1.1.1. Where to Write Primitives |
You may directly insert the C code of your primitives in file "user_dll.c" . This is handy (only) for testing small primitives. You may also create a new C file with a convenient name, like "myfile.c" .
3.21.0.1.1.1.0. Writing Primitives Outside user_dll.c |
/* ------ HEADER SECTION * (copied from user_dll.c) */ #include "tlopen.h" /* ------ PRIMITIVE SECTION * (write your functions here) */ /* ------ INITIALISATION SECTION * (write your initialization code here) */ void init_myfile() { }
Second you must ensure that the initialisation function init_myfile() will be called during the initialisation of Lush. To achieve this, insert a call to init_myfile() in the initialisation function of file "user_dll.c" . Since Lush calls the function init_user_dll() during the initialisation process, it will also run init_myfile() .
Third you must update the list of object files into the Makefile:
3.21.0.1.1.2. Checks and Error Handling |
Since you are programming at the C level, no checks are made at run time. If there is a run time memory integrity error in your function, Lush crashes. Therefore, it is sound to include multiple checks in your function. In particular, you must check thoroughly the validity of the arguments. If you detect an inconsistency, you must call function error() .
3.21.0.1.1.2.0. error ( char *where , char *text , at *arg ) |
See: Error Recovery.
See: A Complete TL/Open Example.
3.21.0.1.1.3. C Function Example |
The first call to error() just prints an error text. The second call to error() passes the illegal argument as a Lush object. It uses the macro NEW_NUMBER() which converts a number into a Lush object.
char *capitalize_nth(s,n) char *s; int n; { static char buffer[256]; char c; int i; /* Checks */ i = strlen(s); if (i>255) error(NIL,"string is too long",NIL); if (n<0 || n>=i) error(NIL,"no nth character", NEW_NUMBER(n)); /* Action */ strcpy( buffer, s); c = buffer[n]; if (c>='a' && c<='z') buffer[n] = c+'A'-'a'; /* Return result in a static area */ return buffer; }
3.21.0.1.2. Writing an Interface Function |
There are two kinds of interface functions in Lush.
3.21.0.1.2.0. DX Interface Functions |
3.21.0.1.2.0.0. Example |
DX(xcapitalize_nth) { ARG_NUMBER(2); ALL_ARGS_EVAL; return new_string( capitalize_nth(ASTRING(1),AINTEGER(2)) );
Let us give a few explanations:
3.21.0.1.2.0.1. Prototypes |
at *xcapitalize_nth(int arg_number, at **arg_array )
We suggest to always use the macro DX() for generating this header. Therefore, your programs will resist a future change in the implementation of these macros.
3.21.0.1.2.0.2. Argument Number Checking |
It is possible however to check the number of arguments by testing directly the value of variable arg_number . In this case you might call ARG_NUMBER(-1) to generate a standard error message about the number of arguments.
3.21.0.1.2.0.2.0. ARG_NUMBER( int n ) |
[#define] |
3.21.0.1.2.0.3. Argument Evaluation |
3.21.0.1.2.0.3.0. ARG_EVAL( int n ) |
[#define] |
3.21.0.1.2.0.3.1. ALL_ARGS_EVAL |
[#define] |
3.21.0.1.2.0.4. Argument Translation |
The complex structures are manipulated by using first APOINTER() to get a Lush object and then by accessing the fields of the Lush object as described in chapter ``Lush Internals''.
3.21.0.1.2.0.4.0. AREAL( int i ) |
[#define] |
3.21.0.1.2.0.4.1. AINTEGER( int i ) |
[#define] |
3.21.0.1.2.0.4.2. AFLT( int i ) |
[#define] |
3.21.0.1.2.0.4.3. ASTRING( int i ) |
[#define] |
3.21.0.1.2.0.4.4. APOINTER( int i ) |
[#define] |
3.21.0.1.2.0.4.5. ACONS( int i ) |
[#define] |
3.21.0.1.2.0.4.6. ALIST( int i ) |
[#define] |
3.21.0.1.2.0.5. Returning from DX Interface Functions |
The simplest method consists in using one of the following alternatives:
return NIL;
returns the empty list which usually means ``false''.
return TLtrue();
returns the symbol t which usually means ``true''.
return NEW_NUMBER(real n); return NEW_NUMBER(int n);
returns a Lush object representing the number n .
return new_string(char *s);
returns a Lush object representing a copy of the zero terminated string s .
3.21.0.1.2.0.6. Complex Argument Syntax Parsing |
For example, here is an alternate definition of the DX interface function of our primitive capitalize_nth() . This new definition checks whether one or two arguments have been provided. If one argument only is provided, this function capitalizes the first character of the string.
DX(xcapitalize_nth) { char *s; ALL_ARGS_EVAL; switch(arg_number) { case 1: s = capitalize_nth( ASTRING(1), 0 ); break; case 2: s = capitalize_nth( ASTRING(1), AINTEGER(2) ); break; default: ARG_NUMBER(-1); /* never return */ } return new_string(s); }
This is easily achieved by testing directly the value of arg_number and by using the following macros.
3.21.0.1.2.0.6.0. ISNUMBER( int i ) |
[#define] |
3.21.0.1.2.0.6.1. ISSTRING( int i ) |
[#define] |
3.21.0.1.2.0.6.2. ISCONS( int i ) |
[#define] |
3.21.0.1.2.0.6.3. ISLIST( int i ) |
[#define] |
3.21.0.1.2.1. DY Interface Functions |
at yname(ARG_LIST) at *ARG_LIST;
This header should be generated using the DY(yname) macro. Like DX interface functions, DY interface functions must check the number of arguments, evaluate the arguments, check the type of the arguments, call the C function and return a Lush object.
No macro or predefined function have been defined for making this task easier. Therefore, DY interface functions are rarely used. They are mostly used for implementing control structures, like if , let , or cond .
3.21.0.1.3. New Function Declaration |
3.21.0.1.3.0. dx_define( char *name, at *(*xfunc)() ) |
3.21.0.1.3.1. dy_define( char *name, at *(*yfunc)() ) |
3.21.0.1.4. Compiling and Debugging |
See: Setting up a Compilation Project Workspace under
Windows.
See: Setting up a Compilation Directory under Unix.
3.21.0.2. Lush Internals |
Here are the main points of this chapter:
3.21.0.2.0. Elementary C Types and Functions |
3.21.0.2.0.0. Universal Pointer Type |
3.21.0.2.0.0.0. ptr |
[#define] |
3.21.0.2.0.1. Floating Point Types |
#define real double #define flt float
Instances of these types can be manipulated by the standard C operators. The type flt however is better to be handled by specific macros.
3.21.0.2.0.1.0. real |
[#define] |
(double)My_real; (float)My_real; (real)My_float; (real)My_double;
3.21.0.2.0.1.1. flt |
[#define] |
There is however a long forgotten possibility to compile a version of Lush based on fixed point arithmetic. Type flt is then defined as long and the usual arithmetic operators do not work. Conscientious programmers therefore use the following macros for dealing with flt numbers. It is however unlikely that you will get into trouble if you do not use them.
3.21.0.2.0.1.2. rtoF(x) |
[#define] |
3.21.0.2.0.1.3. dtoF(x) |
[#define] |
3.21.0.2.0.1.4. Ftor(x) |
[#define] |
3.21.0.2.0.1.5. Ftod(x) |
[#define] |
3.21.0.2.0.1.6. Fadd(x,y) |
[#define] |
3.21.0.2.0.1.7. Fsub(x,y) |
[#define] |
3.21.0.2.0.1.8. Fmul(x,y) |
[#define] |
3.21.0.2.0.1.9. Fdiv(x,y) |
[#define] |
3.21.0.2.0.1.10. Fzero |
[#define] |
3.21.0.2.0.1.11. Fone |
[#define] |
3.21.0.2.0.1.12. Ftwo |
[#define] |
3.21.0.2.0.1.13. Fminus |
[#define] |
3.21.0.2.0.1.14. Fhalf |
[#define] |
3.21.0.2.0.1.15. Fsqrt(x) |
[#define] |
3.21.0.2.0.1.16. Fsin(x) |
[#define] |
3.21.0.2.0.1.17. Fcos(x) |
[#define] |
3.21.0.2.0.1.18. Ftan(x) |
[#define] |
3.21.0.2.0.1.19. Fatn(x) |
[#define] |
3.21.0.2.0.1.20. Fsinh(x) |
[#define] |
3.21.0.2.0.1.21. Fcosh(x) |
[#define] |
3.21.0.2.0.1.22. Ftanh(x) |
[#define] |
3.21.0.2.0.1.23. Flog(x) |
[#define] |
3.21.0.2.0.1.24. Fexp(x) |
[#define] |
3.21.0.2.0.2. Input/Output Functions |
3.21.0.2.0.2.0. void print_char (char c) |
3.21.0.2.0.2.1. void print_string(char *s) |
3.21.0.2.0.2.2. char read_char(void) |
3.21.0.2.0.2.3. char next_char(void) |
Unlike function read_char , this function leaves the character on the pending character queue. The next call to read_char or next_char will return the same character.
3.21.0.2.1. Lush Objects |
3.21.0.2.1.0. at Fundamental Structure |
Here is the C definition of the type at .
typedef struct at; { unsigned int count; unsigned short flags; unsigned short ident; union { struct { /* cons */ struct at *at_car; struct at *at_cdr; } at_cons; real at_number; /* number */ struct { /* external */ ptr at_object; struct class *at_class; } at_extern; } at_union; } at;
In other words, the at structure contains a reference counter count , a couple of flags flags and a three way union named at_union .
p->at_union.at_number
p->at_union.at_cons.at_car p->at_union.at_cons.at_cdr
p->at_union.at_extern.at_class p->at_union.at_extern.at_object
In order to simplify programming, a few shorthands have been defined. For instance, you can write p- Car> instead of p- at_union.at_cons.at_car>.
#define Number at_union.at_number #define Cdr at_union.at_cons.at_cdr #define Car at_union.at_cons.at_car #define Object at_union.at_extern.at_object #define Class at_union.at_extern.at_class
3.21.0.2.1.1. Generic Operations on Lush Objects |
We document here a few utility functions that are useful for all Lush objects regardless of their type:
3.21.0.2.1.1.0. char *print_list(at *p) |
3.21.0.2.1.1.1. char *pname(at *p) |
3.21.0.2.1.1.2. at *read_list(void) |
3.21.0.2.1.1.3. int eq_test(at *p, at *q) |
3.21.0.2.1.1.4. int comp_test(at *p, at *q) |
3.21.0.2.2. Garbage Collection |
3.21.0.2.2.0. Managing Counters in an at Structure |
3.21.0.2.2.0.0. LOCK(at *p) |
[#define] |
For your information, here is the full definition of this macro:
#define LOCK(x) {if (x) (x)->count++;}
3.21.0.2.2.0.1. UNLOCK(at *p) |
[#define] |
For your information, here is the full definition of this macro:
#define UNLOCK(x) {if ((x)&&--((x)->count)==0) purge(x);}
3.21.0.2.2.1. Locking Conventions |
There are three conventions
and one important exception
3.21.0.2.2.1.0. Locking Convention [a]: Arguments |
3.21.0.2.2.1.1. Locking Convention [b]: Adding or Deleting Pointers |
This happens when you change the value of a pointer managed by another Lush object. For example, if you change the value of the pointer to the ``Car'' of a list, you must lock the new value and unlock the old value.
void myrplaca(p,q) at *p, *q; { LOCK(q); UNLOCK(p->Car); p->Car = q; }
However, if you affect this pointer via another C function, like rplaca() , the reference count will be managed by this other C function.
at *myrplaca(p,q) at *p, *q; { return rplaca(p,q); }
3.21.0.2.2.1.2. Locking Convention [c]: Return Values |
This rule has two sides:
Therefore, it is safe to return directly a pointer returned by another C function:
at *abc() { return new_string("abc"); }
In all other cases, you must lock this pointer by hand.
at *myidentity(p) at *p; { LOCK(p); return p; } at *mycar(p) at *p; { at *q = p->Car; LOCK(q); return q; }
3.21.0.2.2.1.3. Locking Convention [d]: Function cons() Is Special |
This exception is designed to overcome a potential inefficiency of convention [c] which leads to unnecessary lock/unlock sequences. For instance, the following function creates the list (1 2 3 4) with the regular function new_cons() :
at *ret1234a() { at *c1, *c2, *c3, *c4; at *n1, *n2, *n3, *n4; n1 = NEW_NUMBER(1); n2 = NEW_NUMBER(2); n3 = NEW_NUMBER(3); n4 = NEW_NUMBER(4); c4 = new_cons(n4, NIL); UNLOCK(n4); /* hot and no longer used */ c3 = new_cons(n3, c4); UNLOCK(n3); UNLOCK(c4); /* hot and no longer used */ c2 = new_cons(n2, c3); UNLOCK(n2); UNLOCK(c3); /* hot and no longer used */ c1 = new_cons(n1, c2); UNLOCK(n1); UNLOCK(c2); /* hot and no longer used */ return c1; /* already locked */ }
The same task is easily achieved with the function cons() , because the at structures returned by the macro NEW_NUMBER() are already locked.
at *ret1234b() { return cons(NEW_NUMBER(1), cons(NEW_NUMBER(2), cons(NEW_NUMBER(3), cons(NEW_NUMBER(4), NIL )))); }
3.21.0.2.2.2. Error Recovery |
This alternate mechanism finds the set of used objects, invokes the destructors on unneeded objects, computes the correct reference counts, returns the unused memory to the pool and finally calls Lush function toplevel to reenter the interpreter interactive loop.
See: Locking Conventions.
See: error ( char *where , char *text , at *arg )
3.21.0.2.2.3. Finding Locking Bugs |
This condition has little consequence during interactive sessions
because Lush errors always occur from time to time. The error garbage
collector will then recompute the counters and clear the problem. An
overlock however may cause to a memory overflow when a Lush program runs
continuously.
Unlike overlocks, underlocks always cause to deadly bugs involving the
corruption of the pool. Lush might crash when you invoke your new
primitive or crash later when it tries to print the underlocked object.
Sometimes it does not crashes, but performs random changes in the
existing Lush structures.
It is therefore important to check the validity of your C functions as early as possible. As a rule of thumb, you should rather risk an overlock than an underlock. You can then use the Lush function used to identify the potential overlock. Here is a simple procedure:
(A) Causing an error will invoke the error garbage collector and make sure that all counters are correct at this point.
? (()) *** read : Cannot evaluate list ** in: (load "$stdin" "$stdout") ** from: (let ((break-hook (lambda () (beep) ... Debug toplevel [y/n] ?n
You can then count the number of Lush objects minus the number of symbols, run your new primitive function, and count again the number of Lush objects minus the number of symbols.
? (- (used) (length (oblist))) = 15843 ? (my-beautiful-c-function) = t ? (- (used) (length (oblist))) = 15847
Changes represent objects that have been created or destroyed by your function. You can check that this new count is correct by causing a new error.
? (()) *** read : Cannot evaluate list ** in: (load "$stdin" "$stdout") ** from: (let ((break-hook (lambda () (beep) ... Debug toplevel [y/n] ?n ? (- (used) (length (oblist))) = 15847
You must get *exactly* the same number here.
Note: Function used returns the number of currently used Lush objects. The error garbage collector however destroys all unreferenced symbols whose value is () . We subtract the number of symbols from the number of objects in order to obtain a quantity that do not depend on this process.
3.21.0.2.3. Lists/Numbers/External Objects |
3.21.0.2.3.0. Lists |
As described above, this information is directly stored in two fields of the at structure located in the branch at_cons of the union at_union .
3.21.0.2.3.0.0. Predicates |
3.21.0.2.3.0.0.0. int CONSP(at *p ) |
[#define] |
3.21.0.2.3.0.0.1. int LISTP(at *p ) |
[#define] |
3.21.0.2.3.0.1. Access |
/* This code loops on the elements of a list <p> */ while (CONSP(p)) { at *q = p->Car; /* q now points to the current element of the list */ p = p->Cdr; }
3.21.0.2.3.0.2. Creation |
There are two functions for creating a list element:
cons() and new_cons() .
Both functions are essentially similar to the Lush function
cons . These functions differ only as described in section
``Locking Conventions''.
3.21.0.2.3.0.2.0. at *new_cons(at *p, at *q ) |
3.21.0.2.3.0.2.1. at *cons(at *p, at *a ) |
The function cons() returns a new list
by prepending element p in front of
list q . The function
cons() is often used for efficiency reasons. As described in
section ``Locking Conventions'', this function does not affect the
reference counts of p and
q .
3.21.0.2.3.0.3. Utilities |
Both functions return a new pointer to a structure at . According to the locking conventions described in section ``Locking Conventions'', this pointer is ``hot'': you must return it or unlock it with the UNLOCK() macro.
3.21.0.2.3.0.3.0. at *rplaca(at *p, at *q ) |
3.21.0.2.3.0.3.1. at *rplacd(at *p, at *q ) |
3.21.0.2.3.1. Numbers |
3.21.0.2.3.1.0. Predicates |
3.21.0.2.3.1.0.0. int NUMBERP(at *p ) |
[#define] |
3.21.0.2.3.1.1. Access |
/* This code loops on the elements of a list <p> */ while (CONSP(p)) { at *q = p->Car; /* q now points to the current element of the list */ p = p->Cdr; }
3.21.0.2.3.1.2. Creation |
There are two functions for creating a list element:
cons() and new_cons() .
Both functions are essentially similar to the Lush function
cons . These functions differ only as described in section
``Locking Conventions''.
3.21.0.2.3.1.2.0. at *new_number(real x) |
3.21.0.2.3.1.2.1. at *NEW_NUMBER(x) |
[#define] |
3.21.0.2.3.2. External Objects |
Therefore, any C structure can be made visible from the Lush level as an external object. This powerful feature raises two problems:
Therefore field p- Class> of the structure points to a structure class. There is a single structure class for each kind of external object.
Sections ``Arrays and Matrices'', ``Strings'', ``Symbols'' and ``Functions'' describe several kinds of external objects. Moreover, the TL/Open protocol allows you to define new Lush objects by defining a new structure class. This is described in section ``User Defined Classes''.
3.21.0.2.3.2.0. Predicates |
3.21.0.2.3.2.0.0. int EXTERNP(at *p, class *someclass ) |
[#define] |
3.21.0.2.3.2.1. Access |
/* This code loops on the elements of a list <p> */ while (CONSP(p)) { at *q = p->Car; /* q now points to the current element of the list */ p = p->Cdr; }
3.21.0.2.3.2.2. Creation |
There are two functions for creating a list element:
cons() and new_cons() .
Both functions are essentially similar to the Lush function
cons . These functions differ only as described in section
``Locking Conventions''.
3.21.0.2.3.2.2.0. at *new_extern(class *cl , void *pt) |
3.21.0.2.4. Arrays/Matrices |
There are several kind of array or matrices:
3.21.0.2.4.0. Predicates |
3.21.0.2.4.1. Access |
/* This code loops on the elements of a list <p> */ while (CONSP(p)) { at *q = p->Car; /* q now points to the current element of the list */ p = p->Cdr; }
3.21.0.2.4.1.0. "struct array" Structure |
struct array *arr = (struct array*)(p->Object);
This structure contains all information relevant to the array or matrix. Here are the main fields of this structure:
short ndim; int dim[MAXDIMS]; int modulo[MAXDIMS]; ptr data;
The arr- ndim> field contains the number of dimensions. The sizes of dimensions are stored as arr- dim[0], ... , arr->dim[ndim-1]>.
The arr- data> field points to a contiguous zone of memory. This pointer is a generic ptr pointer and must be cast to the appropriate type.
at **x = (at **)(arr->data) /* array */ flt *x = (flt *)(arr->data) /* single matrix */ int *x = (int *)(arr->data) /* integer matrix */ int *x = (short*)(arr->data) /* short integer matrix */ real *x = (real *)(arr->data) /* double matrix */ unsigned char *x = (unsigned char*)(arr->data) /* byte matrix */ char *x = (char *)(arr->data) /* packed matrix */
The elements of the array or matrix can then be read or stored as x[offset] . Offsets are computed using the information located in the arr- modulo> field. This is described in the next subsection.
Accessing the elements of an array or of a packed matrix need some further operations:
3.21.0.2.4.1.0.0. real unpack( unsigned char c ) |
3.21.0.2.4.1.0.1. unsigned char pack( real x ) |
3.21.0.2.4.1.1. Computing Offsets |
The offset of a random element A(i[0],...,i[ndim-1]) is thus given by the following multiplicative formula:
i[0] * arr->modulo[0] + ... ... + i[ndim-1] * arr->modulo[ndim-1]
This later expression is a rather inefficient access method. In most cases, you just want to iterate over certain dimensions of the matrix. You can perform efficient iterations on Lush matrices using additive arithmetic. The following fragment of code, for instance, clears the second plane of a three dimensional matrix:
int i,j,off1,off2; flt *data = (flt*)(arr->data); for( i=0, off1=2*arr->modulo[0]; /* first element of second plane */ i<arr->dim[1]; i++, off1+=arr->modulo[1] ) /* move OFF1 to next line */ { for( j=0, off2=off1; /* first element of line */ j<arr->dim[2]; j++, off2+=arr->modulo[2] ) /* move OFF2 to next line */ { data[off2] = Fzero; } }
The modulo (and so the offsets) of an array or a matrix may be negative. Negative modulos are used when you want to access the same data in a reverse order. They are needed for example if you want to rotate an image of a quarter of a turn without duplicating it. They are effectively used by function rotate .
Therefore you should make sure that your C functions properly handle negative modulos. The majority of problems occurs in code looping over the elements of one of several matrices. Certain loops depend on a stopping criterion based on pointer or offset inequalities (e.g. for(p=start; p<start+len; p+=modulo)>) and therefore do not support negative modulos. Most programmers however will find that nothing has to be changed in their code.
3.21.0.2.4.2. Creation |
There are two functions for creating a list element:
cons() and new_cons() .
Both functions are essentially similar to the Lush function
cons . These functions differ only as described in section
``Locking Conventions''.
3.21.0.2.4.2.0. at *array(int ndim, int dim[]) |
3.21.0.2.4.2.1. at *fmatrix(int ndim, int dim[]) |
3.21.0.2.4.2.2. at *imatrix(int ndim, int dim[]) |
3.21.0.2.4.2.3. at *smatrix(int ndim, int dim[]) |
3.21.0.2.4.2.4. at *dmatrix(int ndim, int dim[]) |
3.21.0.2.4.2.5. at *pmatrix(int ndim, int dim[]) |
3.21.0.2.4.2.6. at *submatrix(at *base, int mind[], int maxd[]) |
The tables mind[] and maxd[] specify, for each dimension, the limits of the part of the array or matrix mother referred to by the sub-matrix or sub-array.
If mind[k] is stricly lower than maxd[k] , the submatrix starts on plane mind[k] and finishes on plane maxd[k]-1 in the k -th dimension. This is equivalent to specify a list (mind[k] maxd[k]-1) as the k -th subscript specification of the Lush function submatrix .
If mind[k] is equal to maxd[k] , the submatrix is restricted to plane mind[k] in the k -th dimension. This is equivalent to specify the number mind[k] as the k -th subscript specification of the Lush function submatrix .
3.21.0.2.4.2.7. at *copy_matrix(at *origin, at *target) |
If argument target is equal to NIL, a destination matrix of appropriate size is created. This function always returns the destination matrix.
3.21.0.2.4.2.8. at *copy_any_matrix(at *origin, at *target) |
3.21.0.2.4.3. Utilities |
Both functions return a new pointer to a structure at . According to the locking conventions described in section ``Locking Conventions'', this pointer is ``hot'': you must return it or unlock it with the UNLOCK() macro.
3.21.0.2.4.3.0. struct array *check_vector(at *p, int *n ) |
Finally, this function returns a pointer to the structure array of p .
3.21.0.2.4.3.1. struct array *check_matrix(at *p, int *m, int *n) |
Finally, this function returns a pointer to the structure array of p .
3.21.0.2.4.3.2. Complete Example of Matrix Computation |
at * maddm(v1, v2, ans) at *v1, *v2, *ans; { int n, m, i, j; flt *f1, *f2, *fa, *ff1, *ff2, *ffa; struct array *vv1, *vv2, *vva; /* get the structure array for v1 and v2 */ n = m = 0; vv1 = check_matrix(v1, &n, &m); vv2 = check_matrix(v2, &n, &m); /* create ans if NIL is provided */ if (ans) LOCK(ans); /* because we return ans */ else { int dim[2]; dim[0]=n; dim[1]=m; ans = matrix(2,dim); } /* get the structure array for ans */ vva = check_matrix(ans, &n, &m); /* loop */ for( j=0, ff1 = vv1->data, ff2 = vv2->data, ffa = vva->data; j<n; j++, ffa += vva->modulo[0], ff1 += vv1->modulo[0], ff2 += vv2->modulo[0] ) { for( i=0, f1 = ff1, f2 = ff2, fa = ffa; i<m; i++, fa += vva->modulo[1], f1 += vv1->modulo[1], f2 += vv2->modulo[1] ) { *fa = Fadd(*f1, *f2); } } /* return */ return ans; }
3.21.0.2.4.4. Numerical Recipes Interface |
The pointer returned by these functions is stored in the struct array structure and is used by Lush. Therefore, it should not be altered. Of course this restriction deals with the pointers themselves, and do not restrict the usage of the contents of the matrices they address.
In "Numerical Recipes", a vector is represented by a pointer u to an array of float , double , int or unsigned char . The first element is most often referred to as u[1] . The functions prefixed with get_nr1_XXXvector convert a Lush matrix into such a vector.
In "Numerical Recipes", a matrix is represented by a pointer u to an array of pointers to an array of float , int or double . The first element of the first row of a matrix u is referred to as u[1][1] . The functions get_nr1_XXX convert a Lush matrix into such a matrix.
The functions get_nr0_XXX are similar functions except the subscripts of the vectors and matrices they return range between 0 and n-1 instead of 1 and n .
3.21.0.2.4.4.0. float *get_nr1_vector(at *p, int *n ) |
Following the standard representation of matrices and vectors in ``Numerical Recipes'', the subscripts of the returned pointer range between 1 and n .
3.21.0.2.4.4.1. double *get_nr1_dvector(at *p, int *n ) |
Following the standard representation of matrices and vectors in ``Numerical Recipes'', the subscripts of the returned pointer range between 1 and n .
3.21.0.2.4.4.2. int *get_nr1_ivector(at *p, int *n ) |
Following the standard representation of matrices and vectors in ``Numerical Recipes'', the subscripts of the returned pointer range between 1 and n .
3.21.0.2.4.4.3. short *get_nr1_svector(at *p, int *n ) |
Following the standard representation of matrices and vectors in ``Numerical Recipes'', the subscripts of the returned pointer range between 1 and n .
3.21.0.2.4.4.4. unsigned char *get_nr1_bvector(at *p, int *n ) |
Following the standard representation of matrices and vectors in ``Numerical Recipes'', the subscripts of the returned pointer range between 1 and n .
3.21.0.2.4.4.5. float **get_nr1_matrix(at *p, int *m, int *n) |
Following the standard representation of matrices and vectors in ``Numerical Recipes'', the subscripts of the returned pointer range between 1 and n .
3.21.0.2.4.4.6. double **get_nr1_dmatrix(at *p, int *m, int *n) |
Following the standard representation of matrices and vectors in ``Numerical Recipes'', the subscripts of the returned pointer range between 1 and n .
3.21.0.2.4.4.7. int **get_nr1_imatrix(at *p, int *m, int *n) |
Following the standard representation of matrices and vectors in ``Numerical Recipes'', the subscripts of the returned pointer range between 1 and n .
3.21.0.2.4.4.8. short **get_nr1_smatrix(at *p, int *m, int *n) |
Following the standard representation of matrices and vectors in ``Numerical Recipes'', the subscripts of the returned pointer range between 1 and n .
3.21.0.2.4.4.9. unsigned char **get_nr1_bmatrix(at *p, int *m, int *n) |
Following the standard representation of matrices and vectors in ``Numerical Recipes'', the subscripts of the returned pointer range between 1 and n .
3.21.0.2.4.4.10. float *get_nr0_vector(at *p, int *n ) |
Unlike the standard representation of matrices and vectors in ``Numerical Recipes'', the subscripts of the returned pointer range between 0 and n-1 .
3.21.0.2.4.4.11. double *get_nr0_dvector(at *p, int *n ) |
Unlike the standard representation of matrices and vectors in ``Numerical Recipes'', the subscripts of the returned pointer range between 0 and n-1 .
3.21.0.2.4.4.12. int *get_nr0_ivector(at *p, int *n ) |
Unlike the standard representation of matrices and vectors in ``Numerical Recipes'', the subscripts of the returned pointer range between 0 and n-1 .
3.21.0.2.4.4.13. short *get_nr0_svector(at *p, int *n ) |
Unlike the standard representation of matrices and vectors in ``Numerical Recipes'', the subscripts of the returned pointer range between 0 and n-1 .
3.21.0.2.4.4.14. unsigned char *get_nr0_bvector(at *p, int *n ) |
Unlike the standard representation of matrices and vectors in ``Numerical Recipes'', the subscripts of the returned pointer range between 0 and n-1 .
3.21.0.2.4.4.15. float **get_nr0_matrix(at *p, int *m, int *n) |
Unlike the standard representation of matrices and vectors in ``Numerical Recipes'', the subscripts of the returned pointer range between 0 and n-1 .
3.21.0.2.4.4.16. double **get_nr0_dmatrix(at *p, int *m, int *n) |
Unlike the standard representation of matrices and vectors in ``Numerical Recipes'', the subscripts of the returned pointer range between 0 and n-1 .
3.21.0.2.4.4.17. int **get_nr0_imatrix(at *p, int *m, int *n) |
Unlike the standard representation of matrices and vectors in ``Numerical Recipes'', the subscripts of the returned pointer range between 0 and n-1 .
3.21.0.2.4.4.18. short **get_nr0_smatrix(at *p, int *m, int *n) |
Unlike the standard representation of matrices and vectors in ``Numerical Recipes'', the subscripts of the returned pointer range between 0 and n-1 .
3.21.0.2.4.4.19. unsigned char **get_nr0_bmatrix(at *p, int *m, int *n) |
Unlike the standard representation of matrices and vectors in ``Numerical Recipes'', the subscripts of the returned pointer range between 0 and n-1 .
3.21.0.2.5. Strings |
3.21.0.2.5.0. Predicates |
3.21.0.2.5.1. Access |
/* This code loops on the elements of a list <p> */ while (CONSP(p)) { at *q = p->Car; /* q now points to the current element of the list */ p = p->Cdr; }
3.21.0.2.5.1.0. char *SADD(at *p) |
[#define] |
Therefore, you can write:
if (EXTERNP(p, &string_class)) my_char_ptr = SADD(my_at->Object); else error(NIL,"Not a string",p);
3.21.0.2.5.2. Creation |
There are two functions for creating a list element:
cons() and new_cons() .
Both functions are essentially similar to the Lush function
cons . These functions differ only as described in section
``Locking Conventions''.
3.21.0.2.5.2.0. at *new_string(char *s) |
3.21.0.2.6. Symbols |
3.21.0.2.6.0. Predicates |
3.21.0.2.6.1. Access |
/* This code loops on the elements of a list <p> */ while (CONSP(p)) { at *q = p->Car; /* q now points to the current element of the list */ p = p->Cdr; }
3.21.0.2.6.1.0. at *var_get( at *symbol) |
See: Locking Conventions.
3.21.0.2.6.1.1. void var_set( at *symbol, at *value) |
3.21.0.2.6.2. Creation |
There are two functions for creating a list element:
cons() and new_cons() .
Both functions are essentially similar to the Lush function
cons . These functions differ only as described in section
``Locking Conventions''.
3.21.0.2.6.2.0. at *named( char *s) |
3.21.0.2.6.2.1. at *var_define( char *s) |
It returns a pointer to a symbol whose name is given by the string s . This pointer can only be used as a key for the functions var_set() or var_get() described above. Never lock or unlock this pointer.
3.21.0.2.7. Functions |
Two functions are especially useful for dealing with functions:
3.21.0.2.7.0. at *eval(at *p) |
DY(yif) { register at *q; ifn(CONSP(ARG_LIST) && CONSP(ARG_LIST->Cdr)) error(NIL, "bad 'if' syntax", NIL); if (q=eval(ARG_LIST->Car)) { UNLOCK(q); return eval(ARG_LIST->Cdr->Car); } else return progn(ARG_LIST->Cdr->Cdr); }
3.21.0.2.7.1. at *apply( at *function, at *arglist ) |
real callfunc( func, x ) at *func; real x; { at *arglist; at *result; /* build the argument list */ arglist = cons(new_number(x), NIL); /* calls the function */ result = apply(func,arglist); /* checks the result */ ifn (NUMBERP(result)) error(NIL,"Not a number",result); /* unlock hot pointers */ x = result->Number; UNLOCK(arglist); UNLOCK(result); return x; }
3.21.0.2.8. User Defined Classes |
To define a new primitive class, you must achieve a few simple tasks:
We describe now these successive steps using the example of a class named MYDATA corresponding to a C structure type struct mydata .
struct mydata { at *pl; at *pr; char *s; double d; int i; };
3.21.0.2.8.0. Class Structure Definition |
Assume that our new class is named mydata , the class structure will be initialized by the following C language idiom:
class mydata_class = { /* This is C language */ mydata_dispose, mydata_action, mydata_name, mydata_eval, mydata_listeval, mydata_serialize, mydata_compare, mydata_hash, };
This idiom is not a C++ class definition. The C identifier class actually is a typedef name that refers to the type of the class descriptor structure. The above idiom is nothing more than the definition and initialization of a class descriptor structure. The first few fields of the class descriptor are initialized pointers to the functions mydata_dispose , mydata_action , etc.
C++ programmers will be concerned by the fact that class is a reserved keyword in their preferred language. This name "class" was chosen well before the advent of C++ (or even ANSI C). To fix the consequences of this unfortunate choice, we have introduced a new identifier TLclass available in both C and C++.
TLclass mydata_class = { /* This is C++ language */ mydata_dispose, mydata_action, mydata_name, mydata_eval, mydata_listeval, mydata_serialize, mydata_compare, mydata_hash, };
The Lush interpreter processes objects by calling the appropriate function using the pointers specified by the class descriptor. This is a C implementation of the virtual function concept popularized with the C++ language.
Lush provides simple default functions for all the entries of the class descriptor. These functions are named generic_dispose , generic_action , generic_name , etc. All these functions will be described in the next subsections.
3.21.0.2.8.0.0. Garbage Collection |
3.21.0.2.8.0.0.0. void mydata_dispose(at *p) |
If your data structure contains pointers to other Lush objects, this function must unlock all Lush objects referenced by your data structure. It must also deallocate all the memory privately allocated for your data structure.
void mydata_dispose(at *p) { struct mydata *md = p->Object; /* unlock all Lush objects pointed to by this object */ UNLOCK(md->pl); UNLOCK(md->pr); /* free all memory allocated for this data structure */ if (md->s) free(md->s); free(md); }
3.21.0.2.8.0.0.1. void mydata_action( at *p, void (* action) (q) ) |
void mydata_action(at *p, void (*action)(at *)) { struct mydata *md = p->Object; /* call action on all Lush objects pointed to by this object */ (*action)(md->pl); (*action)(md->pr); }
3.21.0.2.8.0.1. Textual Representation |
You can always use the default function generic_name which returns a simple name of the form "::classname:address" .
char * mydata_name(at *p) { static char buffer[80]; struct mydata *md = p->Object; sprintf(buffer,"::MYDATA:<%s>", md->s); return buffer; }
Defining this function only affects the output function. It does not allow the Lush reader to interpret this string as a Lush object. You can modify the behavior of the Lush reader however by defining macro-characters using function dmc .
3.21.0.2.8.0.2. Evaluation |
3.21.0.2.8.0.2.0. at *mydata_eval(at *p) |
It is generally adequate to use the default function generic_eval() which returns the object itself. All Lush objects (except the symbols) use this default function.
3.21.0.2.8.0.2.1. at *mydata_listeval (at *p,at *q) |
It is generally adequate to use the default function generic_listeval() which displays an error message. All Lush objects (except functions and matrices) use this default function.
3.21.0.2.8.0.3. Serialization |
The object serialization is handled by function mydata_serialize pointed by the class descriptor structure. Serialization however is an optional feature. Specifying the default serialization function pointer generic_serialize will simply cause an error if you attempt to write an object of this class.
Although the serialization process is quite complex, powerful utility functions, named serialize_xxx help writing straightforward serialization functions. All serialization functions take an argument code that indicates which pass of the serialization process is active. All utility functions know how to handle elementary data types during each pass.
3.21.0.2.8.0.3.0. void mydata_serialize(at **p, int code) |
Here is an example of serialization function. for the class MYDATA proposed above as an example. The functions serialize_xxx are described later in this section.
void mydata_serialize(at **p, int code) { struct mydata *md; /* Create object when <code> is <SRZ_READ> */ if (code == SRZ_READ) { if (! (md = malloc(sizeof(struct mydata))) ) error(NIL,"Out of memory", NIL); memset(md, 0, sizeof(struct mydata)); *p = new_extern(&mydata_class, md); } /* Obtain a pointer to the object */ md = (*p)->Object; /* Call serialization function on all members */ serialize_atstar(&md->pl, code); serialize_atstar(&md->pr, code); serialize_string(&md->s, code, -1); serialize_double(&md->d, code); serialize_int(&md->i, code); }
The memory allocated for an object depends sometimes on the values stored in the object. The memory allocated for a vector of at objects, for instance, depends on the size of the vector. It is perfectly legal to read the vector size before allocating memory for the vector data. You must make sure however that the sequence of calls to functions serialize_xxx will not depend on the value of code .
void vector_serialize(at **p, int code) { int i; struct vector *v; if (code == SRZ_READ) { /* Create vector when code is SRZ_READ */ int nelem, memsize; serialize_int(&nelem, code); memsize = sizeof(struct vector) + size * sizeof(at*); if (! (v = malloc(memsize)) ) error(NIL,"Out of memory",NIL); memset(v, 0, memsize); v->nelem = nelem; *p = new_extern(&vector_class, v); } else { /* Catch up with sequence of calls to <serialize_xxx> */ v = (*p)->Object; serialize_int(&v->nelem, code); } /* Serialize the vector elements */ for (i=0; i<v->nelem; i++) serialize_atstar(&v->vec[i], code); }
3.21.0.2.8.0.3.1. void serialize_char(char *data, int code) |
3.21.0.2.8.0.3.2. void serialize_short(short int *data, int code) |
3.21.0.2.8.0.3.3. void serialize_int(int *data, int code) |
3.21.0.2.8.0.3.4. void serialize_float(float *data, int code) |
3.21.0.2.8.0.3.5. void serialize_double(double *data, int code) |
3.21.0.2.8.0.3.6. void serialize_string(char **data, int code, int maxlen) |
If argument maxlen is negative, this string is copied into a memory buffer allocated with malloc . A pointer is stored into the char* variable pointed by argument data .
If argument maxlen is positive, an
error is signaled if the string length exceeds
maxlen character. The string is then copied into the memory
buffer identified by the char*
variable pointed by argument data .
3.21.0.2.8.0.3.7. int serialize_atstar(at **data, int code) |
If this binary representation is an explicit description or a reference of an already read object, function serialize_atstar will store a pointer to this object into the at* variable pointed by argument data and return 0 .
This binary representation however may be a reference to an object
that has not been read yet. Function
serialize_atstar will record the address of the
at* variable pointed to by argument
data and return 1 . The
serialization routines will update this at*
variable after reading the referenced object.
Unlike the other serialize_xxx functions, calling function serialize_atstar with code equal to SRZ_READ does not ensure that the at* variable pointed by argument data will be a valid Lush object when the function returns.
It is therefore forbidden to perform any action that may require that the at* pointers refer to valid Lush objects. Special programming techniques are sometimes necessary:
A Lush hash table object, for instance, records a collection of associations between keys and values. A hashing function computes a numerical quantity (named hash code) using the information associated with each key. This numerical quantity is used to decide where to store the association information in the hash table data structure. Since the keys are arbitrary Lush objects, the hash table serialization function process them using serialize_atstar . When reading a hash table object from the disk, there is no way to compute the hash code within the serialization function. Associations are stored into random locations. A flag however indicates that the hash table must be reorganized. When a hash table function notices this flags, it recomputes all hash codes and moves all associations to the location corresponding to their hash codes.
3.21.0.2.8.0.4. Comparisons |
You may defined these two function as explained below. You can also use the default function pointers generic_compare and generic_hash when initializing the class descriptor structure. These default pointer will just compare the object addresses in memory (physical equality).
3.21.0.2.8.0.4.0. int mydata_compare(at *p, at *q, int order) |
It is not always possible to define an adequate order relation. There is no universaly accepted order relation for complex numbers, for instance. In such a case, function mydata_compare must signal an error when argument order is non zero.
Example:
int mydata_compare(at *p, at *q, int order) { struct mydata *mdp = p->Object; struct mydata *mdq = q->Object; /* Cannot perform ordering on these objects */ if (order) error(NIL,"Cannot rank objects of class |MYDATA|",NIL); /* Compare objects <p> and <q> */ if (! eq_test(mdp->pl, mdq->pl)) return 1; if (! eq_test(mdp->pr, mdq->pr)) return 1; if (strcmp(mdp->s, mdq->s)) return 1; if (mdp->d != mdq->d) return 1; if (mdp->i != mdq->i) return 1; /* Objects <p> and <q> are logically equal */ return 0; }
3.21.0.2.8.0.4.1. unsigned long mydata_hash(at *p) |
Function mydata_hash can use the utility functions hash_value and hash_pointer described later in this section.
Example:
unsigned long mydata_hash(at *p) { char *s; unsigned long x = 0; struct mydata *md = p->Object; s = md->s; while (s && *s) x = (x<<3) ^ (*s++); x = x ^ hash_value(md->pl); x = x ^ hash_value(md->pr); x = (x<<3) ^ md->i ^ *(unsigned long*)&(md->d); return x; }
It is particulary important that functions mydata_hash and mydata_compare implement the same concept of logical equality.
3.21.0.2.8.0.4.2. unsigned long hash_value(at *p) |
3.21.0.2.8.0.4.3. unsigned long hash_pointer(at *p) |
3.21.0.2.8.1. Creation Function Definition |
at *new_mydata(at *lp, at *rp, char *s, double d, int i) { struct mydata *md; /* Allocate */ if (! (md = malloc(sizeof(struct md)) )) error(NIL,"no memory",NIL); /* Initialize */ md->lp = lp; LOCK(lp); md->rp = rp; LOCK(rp); md->s = strdup(s); md->d = d; md->i = i; /* Return <at*> pointer */ return new_extern( &mydata_class, md ); }
You must also write an interface function for calling this function as a Lush primitive.
DX(xnew_mydata) { ALL_ARGS_EVAL; ARG_NUMBER(5); return new_mydata(APOINTER(1), APOINTER(2), ASTRING(3), AREAL(4), AINTEGER(5)); }
3.21.0.2.8.2. User Defined Classes Declaration |
void init_myfile() { class_define("MYDATA",&mydata_class,NIL); dx_define("new_mydata", xnew_mydata); }
Your user defined class is useless unless you define other primitive functions that process the data represented by your new object type.
You can define these functions as usual via the DX or DY conventions. You can check that a Lush object p belongs to your new object class using the usual predicate for external objects:
EXTERNP(p, &mydata_class)
You can then access your data structure through pointer p- Object>:
((struct mydata*)(p->Object))
3.21.0.3. TL/Open Examples |
3.21.0.3.0. String Capitalization |
The first example is the string capitalization function already
described in chapter ``Interfacing a C Function to Lush''. This example
is located in directory
"lushdir/open/examples/capnth" . This directory
contains the following files:
3.21.0.3.0.0. File "capnth/capnth.lsh" |
We suggest that all Lush extension should come with a small Lush file.
Loading file should perform several valuable tasks:
File capnth.lsh illustrates this concept. The user of the string capitalization extension therefore only needs to load file capnth.lsh . This operation searches and loads the extension, incorporate some documentation into the Lush online documentation, and finally performs a few tests.
The first lines of file capnth.lsh are reproduced below. They search and load a TL/Open extension whose name matches the name of the current Lush file. You can copy these lines verbatim into other files.
(let* ((here (dirname file-being-loaded)) (base (basename file-being-loaded "sn")) ) ;; Search along path (let ((oldpath (path))) (path (concat-fname here) (concat-fname here "Debug") (concat-fname here "Release") (concat-fname lushdir "bin") ) (setq here (filepath base ".so|.sl|.dll")) (apply path oldpath ) ) ;; Go (when ~here (error 'mod-load "TL/Open extension not found" base) ) (when winlushp (setq here (upcase here)) ) (when (not (member here (mod-list))) (printf " [+%s]\n" base) (mod-load here) ) )
3.21.0.3.1. Complex Numbers |
The complex number example illustrates more advanced techniques. Complex
numbers are introduced as a TL/Open user defined class, as described by
section ``User Defined Classes''. These new numbers are first class
objects in Lush. You can type a complex number just as it is printed.
You can use the usual operators (eg. +
, - , etc.) on complex numbers.
This example is located in "lushdir/open/examples/complex" . This directory contains the following files:
The following sections give a brief overview of these files. Extensive comments can be found in the files themselves.
3.21.0.3.1.0. File "complex/user_dll.c" |
File user_dll.c is organized in six
sections:
This section also defines and initializes a variable names complex_alloc . This variable represents a pool of preallocated structures managed by the Lush fast memory allocation routines allocate and deallocate . This system is just a fast alternative to the usual memory allocation functions malloc and free .
Function new_complex returns a Lush object representing the complex number specified by its argument. If the imaginary part is zero, this function returns a regular Lush number. Otherwise, this function creates an instance of class complex_class .
Function get_complex takes either a Lush regular number or an instance of class complex_class . It returns the real and the imaginary part of this number.
Function polar_to_cartesian and cartesian_to_polar perform the conversion between the cartesian and the polar representation of a complex number. This is used for implementing transcendental functions.
This section defines in particular a number of operators working on complex numbers (eg. complex+ , complex- , etc.) The syntax of these operators is compatible with the syntax of the usual Lush operators (eg. + , - , etc.). File "complex.lsh" will redefine the usual operator as calls to these new operators. This redefinition allows the use of regular operators with complex numbers.
3.21.0.3.1.1. File "complex/complex.lsh" |
As explained with the previous example, the user of the complex number
extension only needs to load file capnth.lsh
. The bulk of the file complex.lsh
consists of the documentation of the new complex number functions. This
feature relies heavily on the Lush online help system.
Besides providing this documentation, file complex.lsh performs the following tasks:
File complex.lsh is therefore an important part of the complex number package. This file actually plugs the new primitives into the core functions of the Lush interpreter. The cooperation of files user_dll.c and complex.lsh accounts for the seemless integration of complex numbers into the Lush language.
3.21.0.3.2. Numerical Recipes Interface |
The pointer returned by these functions is stored in the struct array structure and is used by Lush. Therefore, it should not be altered. Of course this restriction deals with the pointers themselves, and do not restrict the usage of the contents of the matrices they address.
In "Numerical Recipes", a vector is represented by a pointer u to an array of float , double , int or unsigned char . The first element is most often referred to as u[1] . The functions prefixed with get_nr1_XXXvector convert a Lush matrix into such a vector.
In "Numerical Recipes", a matrix is represented by a pointer u to an array of pointers to an array of float , int or double . The first element of the first row of a matrix u is referred to as u[1][1] . The functions get_nr1_XXX convert a Lush matrix into such a matrix.
The functions get_nr0_XXX are similar functions except the subscripts of the vectors and matrices they return range between 0 and n-1 instead of 1 and n .
3.21.0.3.2.0. File "nr/user_dll.c" |
3.21.0.3.2.0.0. File "nr/user_dll.c": Header Section |
When compiling a dynamically linkable TL/Open extension, the linker will resolve these names with the NR routine rather than the Lush routine. Including the Lush header file would however define the prototype for the Lush routine and not the NR routine.
The solution implemented by file "user_dll.c" consists in redefining temporarily the conflicting names before the inclusion of the Lush header files.
3.21.0.3.2.0.1. File "nr/user_dll.c": NR Utility Routines |
The Numerical Recipes routines call function nrerror whenever they detect an error condition. Our version of this function calls the usual Lush function error .
We also redefine a number of routines for allocating temporary vectors and matrices. These routines are widely used by the Numerical Recipes routines. The new allocation routines call the Lush function error when an error is detected.
3.21.0.3.2.0.2. File "nr/user_dll.c": TL Utility Routines |
The ``TL Utility Routines'' section contains subroutines that help
writing DX interfaces for the Numerical Recipes routines.
3.21.0.3.2.0.3. File "nr/user_dll.c": Primitive Section |
The ``Primitive"" section contains about fifty DX interface functions
for the Numerical Recipes routines. We comment here some of the
techniques used by these interface functions.
This result is achieved by passing small stub functions to the Numerical Routines. These stub functions create a Lush list with the function arguments, and use function apply to call the Lush function identified by a global static variable.
You may notice that these stubs are declared using old style prototypes (often referred to as K&R in reference to the authors of the initial C language, Kernighan and Richie). This is required because the Numerical Recipes routines (1st edition) use K&R prototypes everywhere, and because ANSI protoyped functions and K&R prototyped functions often use different argument passing conventions.
3.21.0.4. TL/Open Extensions Installation |
3.21.0.4.0. TL/Open Extension Installation as a Package. |
Installing a TL/Open extension as a package is certainly the best
solution when the extension has a general purpose and is self contained.
The recommended installation procedure consists in copying both the dynamic library and the Lush file into directory "lushdir/packages" . This simple procedure does not require complex installation scripts.
Directory "lushdir/packages" is automatically searched whenever you call function load .
The user of a package named "linpack" would just type:
? (load "linpack")
to load the package. This simple action will attach the dynamic library and add the corresponding documentation into the help system.
3.21.0.4.1. TL/Open Extension Installation as an Application. |
This viewpoint change has several consequences:
Lush provides two ways to reach that objective. These solutions differ by the extend left to the user for reading your Lush files.
3.21.0.4.1.0. Installation of an "Open" Application Based on Lush |
The user lauches your application using either a shell script (under Unix) or an item of the start menu (under Windows). This operation executes the following command:
<lushdir>/bin/winlush <yourdir>/lib/stdenv.lsh
where lushdir is the Lush root directory and yourdir is the directory where your application is installed.
File "yourdir/lib/stdenv.lsh" should be directly derived from file "lushdir/lib/stdenv.lsh" . This file must however adjust the file search paths and start your application.
Here is a possible Lush code for this adjustment: This code stores the directory name of your application into variable mydir and adds your "lib" directory in the file search path. It defines then variables help-dir-list and help-book-list that specify the directories searched for help files and the list of precompiled help books.
;; Locate application directory. (setq mydir (dirname (dirname file-being-loaded))) ;; Add application lib directory to file search path. (addpath (concat-fname mydir "lib")) ;; Add application help directory to help file search path. (setq help-dir-list (list (concat-fname lushdir "help") (concat-fname mydir "help") ) ) ;; Define relevant help books. (setq help-book-list (list "lush.hlp" "ogre.hlp" "open.hlp" "myapp.hlp" "mytool.lsh" ) )
3.21.0.4.1.1. Installation of a "Closed" Application Based on Lush |
A ``closed'' product based on Lush should be installed in a directory
hierarchy distinct from the Lush directories. This hierarchy should at
least contain the required dynamic library files (DLL or SO files) as
well as a dump file created with function dump
.
The user lauches your application using either a shell script (under Unix) or an item of the start menu (under Windows). This operation executes the following command:
<lushdir>/bin/winlush @<yourdir>/myapp.dump
where lushdir is the Lush root directory and yourdir is the directory where your application is installed.
This command starts Lush and loads the dump file "myapp.dump" that you have prepared using the command dump . During this process, the expression specified by the second argument of command dump is executed. This expression should load all required dynamic libraries (DLL or SO files).
Here is a possible invocation of the dump command. The executable expression code stores the directory name of your application into variable mydir and loads the dynamic library myapp.dll .
(dump "myapp.dump" '(progn (setq mydir (dirname file-being-loaded)) (mod-load (concat-fname mydir "myapp.dll")) ) )
The Lush interpreter then restores the state of the session saved in the dump file. Function startup is then called. This function should define the file search paths according to your needs, possibly initialize the ogre library, and finally run your application.
3.21.0.4.1.2. Installation Script Considerations |
The tool usually provides a dialog window where the user can specify where the application should be installed. The installation program must however find by itself where Lush has been installed. This information is used to create start menu entries that launch your application.
This information is available in the Windows registry. The complete
installation path of WinLush is available by querying the following
registry key:
[HKEY_LOCAL_MACHINE]\Software\Neuristique\WinLush\Path
All installation scripting tools provide ways to examine the registry. You can also manually explore the Windows registry using the program "regedit" usually located in the "Windows" directory.
3.21.0.5. Conclusion |
Let us finish this TL/Open documentation with a few good programming suggestions.