RANDOM NOTES ON CSL/CCL
=======================
The code tries to be in fairly conservative C, but in quite a lot
of places it depends on "the spirit of C" rather than the letter of
the language standards.
"machine.h" contains a catalogue of systems and compilers that the
code has AT SOME STAGE been at least tested once on. At present the
most tested configurations are
Windows, Watcom C 11.5
Linux x86, gcc
and other cases may well have glitches remaining. But I hope that the long
list of tested cases will give encouragement that the code is respectably
portable. Only the Windows version (using The Microsoft Foundation Classes)
has a currently-supported windowed interface: other versions use a command-
line style. Both Mac and X have at times experimenented with the "StdWin"
application framework but the code here does not guarantee to work with it
still or well.
I have adopted an indentation and code layout style that I like. I am aware
that it is not the default one supported my emacs etc, but I have been using
it for a long while and feel very set in my ways.
This Lisp uses an agressive garbage collector that MUST be able to identify
all root pointers reliably. C local variables are not marked by the GC, and
because the GC can relocate things a reference held in a simple C variable
is NOT SAFE across anything that could cause a GC. The macros push() and
pop() place data on a Lisp-safe stack. The coding conventions needed to make
GC safe are ones that take a significant time to get used to. They are not
coder-friendly. If you breach them then the code may work most of the time
but fail later in a messy way. I can only say "you have been warned"!
This Lisp needs to control error recovery and general unwinding. Thus any
function call that could possibly fail MUST be followed by a test to
see if it did. In some cases this will need to do pop() operations (which are
NOT automatically balanced across procedure calls: the programmer has to
take full responsibility for matchiing push and pops). See the code for
examples of what is needed. Again this is not coder-friendly and errors in
applying the conventions can have delayed and unpleasant effect. Again "you
have been warned"
Common Lisp supports functions that return multiple values. Thus the normal
simple case MUST use the onevalue() macro to indicate that what is returned
is a singleton. Failure to do this can mess up error recovery or the
garbage collector.
The above three issues lead to significantly ugly and delicate code. They
are easy for somebody whos is not neurotic enough to overlook. The
trouble that they can lead to will often be deferred, only showing up
occasionally on garbage collection or when recovering from an unrelated
exception. This situation where errors can go undetected for a long while
makes the need for care over these issues especially high priority!
For several platforms it seems to be a really good thing to be able to
respond well to asynchronous events. These may be "^C", or mouse clicks.
Especially with windowed systems some form of threading would be very very
helpful. This is hard wrt portability. Thus for essentially all systems I
use the idea of a "tick stream" of more or less regular events that can
be used to activate polling etc. Having tried genuine timer-based systems and
having had a LOT of trouble with them I mostly use a software count-down
scheme under the guard SOFTWARE_TICKS. If you feel tempted to discard this
in favour of real threads or of nothing then consider the issues of back-
porting to (eg) the Mac, or DOS where you must poll before even ^C is
noticed. I do not want this code to lose portability, please!
The basic data representation here is that small integers (and characters)
are represented as value types, with some of the low 3 bits used as tags.
Cons cells, vectors, big-numbers etc are handled by reference. By arranging
that all items are stored aligne dto an 8-byte boundary the low 3 bits
in a pointer are available for use as tags. More details can be seen in
"tags.h". The most significant bit in a pointer is used for garbage
collection, so heap memory must all be allocated in one half of the
machine'e address space (it does not matter which!).
The Lisp system here was designed for delivery of code that had already
been developed and was basically working. It was not designed as a
development environment. This means that in some respects it is
inflexible! For instance there is a Lisp to C compiler, but it is
configured so that the generated C is statically linked into the Lisp.
The Lisp does not have very clever windowed interfaces, browsers etc.
The Lisp compiler has its master version coded in RLISP (an Algol-like
surface syntax for Lisp, popularised by the REDUCE algebra system). A
yacc-based parser can translate this into raw Lisp, but enhancements would
be better made to the RLISP version.
I leave a notation that sort-of looks like an attempt to nest comments
in places in the code where I know there are issues that have not been
fully resolved. Eg comments such as
/* /* This is not yet fully up to Common Lisp specification */
etc etc. I do this because I can then make (some) C compilers generate
warnings that remind me of these worries. Ideally I would fix them all! Or
ideally C would have been specified with a #warn as well as and #error
preprocessor directive!
The notation "/* Signature ... */" present towards the top of each file
is maintained using a utility called "filesign". It provides a checksum
on the file, which I find much more reliable as a way of confirming
versions than date-stamps, especially when files are copied between
a number of different machines, perhaps in different time-zones.
[more random comments follow...]
double-execute() and undouble-execute() are like trace() and untrace() but
cause the nominated functions to double their costs. Only at first actually
does anything for functions with fixed numbers of args.
PRESERVE optional args. First specifies a restart function
to be called, the second is a banner to display when the image is reloaded.
Reduce now uses this mechanism to gets its banner displayed. Third arg can be
stuff to pass to the restart function - passing is via a text-string interface
so it is a bit delicate!
Lexical closures supported in the bytecode compiler (for variable reference
but not GO or RETURN-FROM) and 3 new opcodes (closure, loadlex, storelex)
introduced in bytes1.c to support same.
By default the MSB of pointers is now determined at run time (rather than
being a manifest value compiled into the code).
Support for calls to functions with large numbers of arguments is
improved. In particular in Common Lisp mode it is possible to have
more than 50 args.
Line-length overflow checking for printing of big numbers implemented.
binary printing for numbers, and case fold up as well as down, supported.
-------------------------------------------------------------------------------
DATA REPRESENTATION
===================
All objects in CSL are represented by 32-bit words(*), and each object in
memory is aligned at an 8-byte boundary. The bottom 3 bits of a word
aare used as tags, and indicate how the rest of the word should be
interpreted. In some cases the rest of the word is just direct information
(eg small integers are handled this way) and then the 0x8-bit is used by
the garbage collecter. In other cases the 32-bit word contains the address
in memory of some object, and in that case its most significant bit is
reserved for use by the garbage collector. Thus all valid addresses must have
the same sign (but this can be either + or -). Many objects in memory have
a header word at their start that indicates their length and gives more
details about their type. The encoding of object lengths means that the
largest possible single object in CSL would be 4 Mbytes long. Also CSL
memory is allocated in chunks (typically 256 Kbytes) and that puts another,
usually more severe, limit on maximum object size. This may matter for
arrays and bignums.
(*) well on some machines they may be 64-bits but not much changes!
The coding in the low bits of a word is as follows:
x x x | x 0 0 0 cons cell (and NIL in Common Lisp mode)
x x x | G 0 0 1 28-bit integer (G = garbage collector bit)
x x x | G 0 1 0 characters, vec-hdrs, other oddities (immediate)
x x x | G 0 1 1 28-bit short float if Common Lisp mode
x x x | x 1 0 0 a symbol
x x x | x 1 0 1 bignum. {rational number, complex number}
x x x | x 1 1 0 Lisp vector (includes strings etc)
x x x | x 1 1 1 pointer to boxed floating point number
Pointers reserve the 0x80000000L bit for the garbage collector,
assuming that all addresses are in the same half of the space. This
of course is a slightly dodgy assumption that may be false on some
computers. On 64-bit machines it is again the top bit that is used by
the GC.
Header words in the vector heap
If the bottom 3 bits of a word are 010 (TAG_ODDS) the word is
treated as immediate data. In that case the 1000 bit (GC_BIT_I)
is kept free for use as a mark bit. The next bits up classify the
object:
| x x 0 0 | * 0 1 0 valid immediate date in heap
but can also be used as vector headers
| x x 0 1 | * 0 1 0 symbol headers
| x x 1 0 | * 0 1 0 number headers
| x x 1 1 | * 0 1 0 vector headers
Data that could be in the heap or on the stack decodes further:
x x | 0 0 0 0 | * 0 1 0 character
x x | 0 1 0 0 | * 0 1 0 handle/offset to BPS page
x x | 1 0 0 0 | * 0 1 0 [handle/offset to literal vector]
x x | 1 1 0 0 | * 0 1 0 special marker (SPID) left on stack
to help interpreter
The remaining 24 bits of the word are available for data. The BPS and
literal vector handles in this case are for a segmented memory
implementation, and the 24 bit address has its top 10 bits identifying
a page number, then 14 bits giving a WORD offset into the associated
page, which can thus be up to 64 Kbytes large. Or more depending on
PAGE_BITS. Literal vectors done this way are not used at present.
For Standard Lisp only 8 bits of character information is needed - the
remaining bits are there to support font etc information in a Common
Lisp world.
. . . . | g f 0 1 | * 0 1 0 symbol header
g global variable
f fluid (special) variable
The remaining bits in the word are used as follows
00000100 symbol names a special form
00000200 symbol has a definition as a macro
00000400 an unprinted gensym (so print-name is not complete yet)
00000800 has a definition in C from the C-coded kernel
00001000 just carries a code-pointer (codep test)
00002000 any gensym, printed or not
000fc000 fastget code in this is used as a p-list indicator
00100000 traced
00200000 is external in its home package
ffc00000 reachability in first 10 packages!!!
The remaining header words hold the length (in bytes) of the object
(including the length of the header word) in the upper 22 bits of the
word. This puts a limit of 4 Mbytes on the largest possible single
object in this Lisp.
0 0 | 0 0 1 0 | * 0 1 0 bignum header word
0 0 | 0 1 1 0 | * 0 1 0 ratnum
0 0 | 1 0 1 0 | * 0 1 0 complex number
0 0 | 1 1 1 0 | * 0 1 0 NOT USED (non-float number)
0 1 | 0 0 1 0 | * 0 1 0 single float
0 1 | 0 1 1 0 | * 0 1 0 double float
0 1 | 1 0 1 0 | * 0 1 0 long float
0 1 | 1 1 1 0 | * 0 1 0 NOT USED (floating number)
Note that headers for numbers are 0x|xx10|x010 and the 0x100 bit flags
floating point cases
n n | n 0 1 1 | * 0 1 0 bitvector with nnn bits in last byte
0 0 | 0 1 1 1 | * 0 1 0 string
0 0 | 1 0 1 1 | * 0 1 0 (bitvector)
0 0 | 1 1 1 1 | * 0 1 0 simple vector
0 1 | 0 0 1 1 | * 0 1 0 (bitvector)
0 1 | 0 1 1 1 | * 0 1 0 BPS
0 1 | 1 0 1 1 | * 0 1 0 (bitvector)
0 1 | 1 1 1 1 | * 0 1 0 hash table
1 0 | 0 0 1 1 | * 0 1 0 (bitvector)
1 0 | 0 1 1 1 | * 0 1 0 SPARE contains binary data
1 0 | 1 0 1 1 | * 0 1 0 (bitvector)
1 0 | 1 1 1 1 | * 0 1 0 Common Lisp array (header block)
1 1 | 0 0 1 1 | * 0 1 0 (bitvector)
1 1 | 0 1 1 1 | * 0 1 0 encapsulated stack pointer
1 1 | 1 0 1 1 | * 0 1 0 (bitvector)
1 1 | 1 1 1 1 | * 0 1 0 Common Lisp structure
(use BPS for vector of 8-bit ints)
1 0 | 0 0 1 0 | * 0 1 0 vector of 16-bit integers
1 0 | 0 1 1 0 | * 0 1 0 vector of 32-bit integers
1 0 | 1 0 1 0 | * 0 1 0 MIXED1 [3 words of pointers, then binary]
1 0 | 1 1 1 0 | * 0 1 0 MIXED2
1 1 | 0 0 1 0 | * 0 1 0 vector of single-precision floats
1 1 | 0 1 1 0 | * 0 1 0 vector of double-precision floats
1 1 | 1 0 1 0 | * 0 1 0 MIXED3
1 1 | 1 1 1 0 | * 0 1 0 Stream handle (aka MIXED4)
-------------------------------------------------------------------------------
FAST PROPERTY ACCESS IN CSL/CCL
===============================
The "fast-get" facility in CSL arranges that for a number of user-specified
property names (used with both GET and FLAGP) the access has constant cost.
Normally access to a property involves searching a property list, and if one
symbol has many properties this can be tedious - with a worst case in the
fairly common circumstance that the property is not found.
To speed up CSL I allocate 6 bits in a symbol header that influence how that
symbol is treated when used as a property name. One code is reserved to
mean "use a normal property list", leaving 63 codes for fast access. The
first of these is reserved for a property called 'NONCOM because the built-in
ORDERP function wants to use this for compatibility with REDUCE(!!).
A tag must be marked as "fast" before any properties with that indicated are
set up. If this constraint is not adhered to then properties can end up
stored in inconsistent or redundant forms. The protocol is to select an
integer in the range 0 to 63 for each tag to be handled specially, and then
to make a call such as
(symbol-make-fastget 'noncom 0)
(symbol-make-fastget 'my_property_name 1)
(flag '(a b c) 'noncom)
(put 'x 'my_property_name 'y)
or (setf (get 'x 'my_property_name) 'y)
The fast-get status of a symbol can be inspected using
(symbol-fast-get 'x nil)
or reset to have no special treatment (please do not do this!)
(symbol-fast-get 'x -1)
When a symbol is given a "fast" property a vector of length 63 is created
and a word in the symbol's header is made to point to it. The vector contains
either property values ir a marker that indicates that no property is
present. A call (symbol-fastgets 'x) can retrieve the vector and can be
useful while debugging, maybe.
If you inspect the property list of an object (using the PLIST function) then
any fast properties are extracted from the vector and built onto the front
of the property list as returned. Of course this means that altering the
property list using RPLACx may not be useful!
The function GET retrieves a property. In Common Lisp mode the three-argument
version (GET a b c) allows c to be returned as a default value if the
property sought is not present. The two-argument version can not distinguish
between a property whose value is NIL and not having a property present
at all. The function FLAGP returns T if a property is present (whatever
value is associated, including the case where the stored value is NIL). The
function FLAG just sets properties, giving them the value T in a somewhat
arbitrary way.
Clearly use of fast gets consumes memory, but because many of the extra
vectors contain many NIL elements they compress well in image files, which
therefore do not grow too badly. To decide which properties are most
critical you can run tests using the profiled version of the bytecode
interpreter (bytes.c rather than bytes1.c), and after a run call the
function (BYTECOUNTS). You need to adjust your build sequence so that
the file bytes.c is compiled with RECORD_GET defined, e.g. by putting
-DRECORD_GET
among the flags passed to your C compiler. Note that this option will slow
things down noticeably. The output from BYTECOUNTS indicates which property
tags were used, and how many unsuccessful searches for each tag occurred. This
latter information is collected because unsuccessful searches are the worst
case for a traditional property list search.
Note that when a property is handled "fast" it is not stored on the regular
property list (the only information about it is in the vector). Thus if the
most commonly used properties have been handled this way the average length
of property lists will shrink and so access to all other properties is
slightly speeded up too.
By editing FASTGET_SIZE in the source file "tags.h" if would be possible to
make the symbol extension vectors smaller than 63 items long, so in
cases where a much smaller set of tags is important the system can
be configured to save some memory.
-----------------------------------------------------------------------
[This issue was responded to during the Axiom customisation process]
Subject: Interface for dynamic opening and closing of libraries
Arthur:
I've discussed the specification of the "dynamic libraries" interface with
Barry and this is what we think we need. For a user's own code, we will
always know the full pathname of the library it is in, so we can load the
module explicitly.
1. A function that loads a particular module from a particular library, e.g.
(load-module <module> <pathname>)
The library need not be open for input. You might wish to have separate
open-library, load-module, and close-library operations if that is more
efficient.
2. A function that opens a library for output, e.g.
(open-library <pathname>)
The function need not be responsible for checking if the library is open
already.
3. A function that reads a lisp source file, translates it into byte codes,
and writes the results into a named library, e.g.
(fasl-out <filename> <library>)
This could either:
(a) Overwrite an existing module with the same name (our preferred option),
or
(b) replace the entire library if it already exists.
4. A function that closes an open library and, if option 3a above is
implemented, does all the necessary tidying-up, compaction etc, e.g.
(close-library <pathname>)
5. Facilities for setting the library search path from within Lisp:
(a) deleting a library from the search path;
(b) adding a new library to the front of the search path;
(c) adding a new library to the end of the search path.
------------------------------------------------------------------------
This shows the proportionate use of the various byte opcodes
on one particular test run. The information was collected while I was
trying to optimise the bytecode design. If any elaborate bytecode or
combination of bytecodes was getting very heavy use I might provide
special-case variants etc. Of course with different test programs the
profile may end up somewhat different.
7.3400 LOADLOC6
3.8160 LOADFREE
3.6256 CARLOC6
2.8646 LOADFREE2
2.7618 EXIT
2.6747 PUSH
2.6054 LOADLOC1
2.2990 LITGET
2.2175 LOADLOC3
2.0495 STOREFREE
2.0072 JUMPNIL_L
2.0046 CONS
1.8176 BUILTIN2
1.7415 JUMPNIL
1.7210 LOADLOC0
1.5494 JUMPST1NI
1.5250 BUILTIN1
1.4593 JUMPNIL_B
1.4564 CALL1
1.4206 STORELOC1
1.3783 PUSHNIL2
1.3783 LOSE2
1.3624 FREERSTR
1.3624 FREEBIND
1.2805 JUMPT
1.2565 JUMP_B
1.1196 CDR
1.0389 LOADLOC5
1.0384 VNIL
0.9265 STORELOC2
0.9265 STORELOC0
0.9200 JUMPNEQCA
0.8966 CAR
0.8812 LOADLOC10
0.8712 JUMP_BL
0.8607 LOADLOC2
0.8244 STOREFREE
0.7828 LOADLOC4
0.7735 GREATERP
0.7659 JUMPNFLAG
0.7408 JUMPLITNE
0.7264 CALL2_1
0.7089 LOADLIT
0.6966 ADD1
0.6903 CARLOC2
0.6749 LOADLOC7
0.6739 JUMPT_BL
0.6556 CADR
0.6501 JUMPNATOM
0.6448 JUMPFLAGP
0.6395 CALL2
0.6369 JUMPLITEQ
0.6357 LOSE
0.6287 STORELOC
0.6274 SWOP
0.6003 LOADLIT4
0.5577 STORELOC5
0.5397 APPLY1
0.5004 LOADLOC9
0.4910 CDRLOC2
0.4772 JUMPL2T
0.4630 CARLOC10
0.4404 LOADLIT1
0.4196 CARLOC0
0.4178 CDRLOC0
0.4173 JUMPNIL_B
0.4029 JUMPFREE3
0.3861 STORELOC4
0.3767 STORELOC3
0.3659 NCONS
0.3613 LOADLOC8
0.3589 PUSHNIL
0.3164 JUMPB2NIL
0.3108 JUMPFREEN
0.3030 JUMPATOM
0.2955 JUMPFREET
0.2879 PUSHNILS
0.2823 LOADLOC
0.2672 CALL1_3
0.2587 CARLOC1
0.2554 CDDR
0.2515 CARLOC5
0.2469 CALL1_1
0.2457 LOADLOC11
0.2298 LOSES
0.2275 PUSHNIL3
0.2109 JUMPLIT2N
0.2024 NUMBERP
0.2013 CALL3
0.2004 JUMPT_L
0.1952 JUMPL0T
0.1930 CALL2R
0.1910 JUMPLIT1N
0.1840 JUMPL1T
0.1835 LOSE3
0.1789 STORELOC7
0.1785 CALL2_4
0.1706 CALL1_2
0.1696 CAARLOC2
0.1673 POP
0.1645 JUMPL1NIL
0.1582 LOADFREE1
0.1533 JCALL
0.1516 JUMPNE
0.1512 CARLOC4
0.1463 LOADLIT2
0.1418 JUMPL1NAT
0.1414 CARLOC11
0.1382 CDRLOC1
0.1373 CDRLOC5
0.1352 JUMPL0NIL
0.1350 JUMPB1NIL
0.1251 LOADLIT3
0.1238 LOADLIT5
0.1180 LOADFREE3
0.1173 CDAR
0.1163 CARLOC7
0.1127 CAAR
0.1120 JUMPST0NI
0.1108 ACONS
0.1080 LOADLIT6
0.1077 XCONS
0.1061 CALLN
0.0986 JUMPT_B
0.0973 CALL1_5
0.0959 CARLOC9
0.0890 LOADFREE4
----------------------------------------------------------------------
[some comments from when NAG were getting Axiom support fully up to
scratch: these are now probably only of historical interest!]
15 May [PAB] sys*.c: get_truename should preserve '/' at end of string
print.c: fix memory leak in Ltruename
21 May [PAB] syscwin.c: use STARTF_USESTDHANDLES
17 Jun [MCD] scandir.c: Changed the NT version of scan_files to check
for a trailing '\' before adding '\*.*'.
12 Aug [MCD] all files: merged with latest version from ACN.
14 Aug [MCD] fns3.c: Added C-coded version of subseq and auxilliaries.
22 Aug [TTT] arith03.c arith11.c: changed quotib,Cremainder to treat
properly the case (DIVIDE -134217728 134217728)
23 Aug [TTT] fns1.c: changed a comment to refer to *break-loop* not *break-function*
30 Aug [TTT] machine.h: SYS_TIMES macro name changed because of collision
with system-defined macro in HP/UX
also affected :sys.h restart.c sysunix.c sysvms.c sysxwin.c
20 Sep [TTT] fns3.c: added hashtable-flavour thingies
9 Oct [TTT] sockets.c: added glnag calls for all platforms
added NANQ support
28 Oct [TTT] scandir.c: use realloc_wnull instead of realloc
to cure SunOS4.1.2 problems with realloc(0,...)
20 Nov [TTT] read.c: typo Lrdfn Lddfn fixed
20 Nov [TTT] read.c: Lrdf4 : changed the Lopen to honour :if-does-not-exist
keyword
21 Nov [MCD] All files: merged latest changes from Arthur.
2 Dec [TTT] gc.c : added timnag_ license management call in reclaim
2 Dec [TTT] csl.c: added init_lm call in ENTRYPOINT
3 Dec [TTT] sockets.c: changed product codes to AX???23NA
added nullary init_lm function
------1997
24 Jan [TTT] sysunix.c: changed mkdir(filename,0770) to
mkdir(filename,0777) to respect world setting of umask
in create_directory.
23 April [MCD] externs.h restart.c csl.c fasl.c fns1.c fns2.c eval2.c
Introduced mechanism (using symbol_protect_flag) to toggle
protection of symbols in kernel.
10 July [MCD] csl.c Added initialisation of symbol_protect_flag to 1
syscwin.c: Fixed bug in truename which stuck extra trailing
slashes on directory names, causing directoryp to fail.
19 Sep [MCD] csl.c : Disabled CCL break messages on interrupt
3 Dec [PAB] read.c, gc.c: Added call-counting mode to mapstore
(yes, it is a hack).
-------------------------------------------------------------------------
I have just manufactured a version of CSL that is (perhaps) easy to call
from other C code. What follows is the main chunk of code that shows
what the interface is. What this means wrt REDUCE is that I will want to
manufacture an alternative to (BEGIN) that reads-simplifies-prints just
one REDUCE expression/command at a time rather than the current single
function that gobbles things for ever....
/*
* The next fragment of code is to help with the use of CSL (and hence
* packages written in Lisp and supported under CSL) as OEM products
* embedded within larger C-coded packages. There is (of course) a
* significant issue about clashes between the names of external symbols
* if CSL is to be linked with anything else, but I will not worry about that
* just yet.
* The protocol for calling Lisp code from C is as follows:
*
* cslstart(argc, argv, writer);allocate memory and Lisp heap etc. Args
* should be "as if" CSL was being called
* directly and this was the main entrypoint.
* The extra arg accepts output from this
* stage. Use NULL to get standard I/O.
* execute_lisp_function(fname, reader, writer);
* fname is a (C) string that names a Lisp
* function of 0 args. This is called with
* stdin/stdout access redirected to use the
* two character-at-a-time functions passed
* down. [Value returned indicates if
* the evaluation succeeded?]
* cslfinish(writer); Tidies up ready to stop.
*/
int execute_lisp_function(char *fname,
character_reader *r, character_writer *w)
{
Lisp_Object nil;
Lisp_Object ff = make_undefined_symbol(fname);
nil = C_nil;
if (exception_pending()) return 1; /* Failed to make the symbol */
procedural_input = r;
procedural_output = w;
Lapply0(nil, ff);
procedural_input = NULL;
procedural_output = NULL;
nil = C_nil;
if (exception_pending()) return 2; /* Failure during evaluation */
return 0;
}
#ifdef SAMPLE_OF_PROCEDURAL_INTERFACE
static char ibuff[100], obuff[100];
static int ibufp = 0, obufp = 0;
static int iget()
{
int c = ibuff[ibufp++];
if (c == 0) return EOF;
else return c;
}
static void iput(int c)
{
if (obufp < sizeof(obuff)-1)
{ obuff[obufp++] = c;
obuff[obufp] = 0;
}
}
#endif
int MS_CDECL main(int argc, char *argv[])
{
cslstart(argc, argv, NULL);
#ifdef SAMPLE_OF_PROCEDURAL_INTERFACE
strcpy(ibuff, "(print '(a b c d))");
execute_lisp_function("oem-supervisor", iget, iput);
printf("Buffered output is <%s>\n", obuff);
#else
cslaction();
#endif
my_exit(cslfinish(NULL));
return 0;
}
...............................end
Arthur Norman. 2002