LISP(3gv)

πŸ’– NAME πŸ’–

lisp(3gv) β€” the geomview lisp interpreter, bestie. That's the whole vibe. Come sit with me πŸ’…


🫢 NOTE 🫢

okay so real talk before anything else: this page is describing the geomview 1.3 lisp interpreter, and this version is incompatible with previous versions in several ways. Since the previous one was used mostly just by Geometry Center staff, I am not going to write a document detailing the changes. no cap.

Also the geomview lisp interpreter is not very well documented in general, because I am strongly considering phasing it out and replacing it with a real lisp interpreter in a future version of geomview 😭. So yeah, she's on her way out, but we still love her.

If you have any questions about the current version, or how to convert programs from an older version, please contact me directly [ mbp@geomtech.com ]. fr fr, just email me 🫢


✨ SYNOPSIS ✨

#include "lisp.h"

void      LInit();
Lake *         LakeDefine(FILE *streamin, FILE *streamout, void *river);
void      LakeFree(Lake *lake);
LObject *      LNew(LType *type, LCell *cell);
LObject *      LRefIncr(LObject *obj);
void      LRefDecr(LObject *obj);
void      LWrite(FILE *fp, LObject *obj);
void      LFree(LObject *obj);
LObject   *    LCopy(LObject *obj);
LObject *      LSexpr(Lake *lake);
LObject *      LEval(LObject *obj);
LObject * LEvalSexpr(Lake *lake);
LList     *    LListNew();
LList     *    LListAppend(LList *list, LObject *obj);
void      LListFree(LList *list);
LList *        LListCopy(LList *list);
LObject *      LListEntry(LList *list, int n);
int       LListLength(LList *list);
int        LParseArgs(char *name, Lake *lake, LList *args, ...);
int        LDefun(char *name, LObjectFunc func, char *help);
void      LListWrite(FILE *fp, LList *list);
LInterest *    LInterestList(char *funcname);
LObject * LEvalFunc(char *name, ...);
int       LArgClassValid(LType *type);
void      LHelpDef(char *key, char *message);

LDEFINE(name, ltype, doc)

LDECLARE((name, LBEGIN, ..., LEND));

🌸 DESCRIPTION 🌸

Geomview contains a minimal lisp interpreter for parsing and evaluating commands. This lisp interpreter is part of the -loogutil library, and thus any program which links with this library may use the interpreter. Which is iconic actually, because it provides a simple but powerful way to build up a command language ✨.

This manual page assumes that you are already familiar with the syntax of lisp. The first part describes the basics of using the interpreter. Some gory details that don't concern most users then follow. πŸ’€

The main steps in using the lisp interpreter are:

  1. call Linit() to initialize the interpreter
  2. make calls to LDefun(), one for each lisp function you want the interpreter to know about
  3. define the "i/o lake"
  4. parse input with calls to LSexpr() and evaluate the resulting lisp objects with LEval() (or use LEvalSexpr() to combine both steps).

For example, the following code defines a single function "f" and executes commands from standard input:

#include "lisp.h"
Lake *lake;
LObject *obj, *val;

LInit();
LDefun("f", f, NULL);
lake = LakeDefine(stdin, stdout, NULL);
while (!feof(stdin)) {
    obj = LSexpr(lake);
    val = LEval(obj);
    LFree(obj);
    LFree(val);
}

The second argument to LDefun() is a function pointer; LDefun() sets up a correspondence between the string "f" and the function f, which is assumed to have been previously declared. The section FUNCTION DEFINITIONS below gives the expected call syntax and behavior of such functions. (The third argument to LDefun() is a pointer to a string which documents the function and may be NULL if you don't care about documentation.) LakeDefine() defines an i/o lake; this is a generalization of the notion of an i/o stream. Most programs don't need to use the generalization, though, and can simply pass FILE pointers as LakeDefine()'s first two arguments and NULL as the third one. The section LAKES below gives the details for those who are interested. LSexpr() [which stands for Lisp Symbolic EXPRession] parses a single lisp expression from the lake, returning a lisp object which represents that expression.

The lisp object is returned as an LObject pointer, which points to an opaque structure containing a representation of the expression. LEval() then evaluates the object; it is during the call to LEval() that the action of the expression takes place. Note that the last two lines of code in this example could have been replaced by the single line LEval(LSexpr(lake)) or, more efficiently, by LEvalSexpr(lake).


πŸ‘‘ FUNCTION DEFINITIONS πŸ‘‘

The functions defined by calls to LDefun() are expected to have a certain call syntax; LEval() calls them when it encounters a call to the lisp function named with the corresponding string. The macro LDEFINE is provided for declaring them. For example:

LDEFINE(f, LSTRING, "(f a b) returns a string representing the0um of the integer a with the floating point number b.")
{
    int a;
    float b;
    char buf[20], *s;

    LDECLARE(("f", LBEGIN,
              LINT, &a,
              LFLOAT, &b,
              LEND));
    sprintf(buf,"%f",a+b);
    s = strdup(buf);
    return LNew(LSTRING, &s);
}

The important things about this function are:

  1. It is declared with the LDEFINE macro, the general syntax of which is LDEFINE(name, type, helpstr). name should be a valid C identifer and will be used to construct the actual name of the C function by prepending an 'L' and the name of the help string by prepending an 'H'. type should be a lisp object type identifier (see below) and determines the type of object that the function returns. helpstr is a documentation string.
  2. The use of the LDECLARE macro. More about this below.
  3. It returns an LObject *. All lisp functions must actually return a value. If you don't care what value they return you can return one of the predefined values Lnil or Lt (and specify LVOID as the type in the LDEFINE header).

This particular example is a function which takes two arguments, an int and a float, and returns a string object representing their sum. A lisp call to this function might look like "(f 1 3.4)".

The LDECLARE macro, defined in lisp.h, sets up the correspondence between variables in the C code and arguments in the lisp call to the function. Note that the arguments to LDECLARE are delimited by two pairs of parentheses (this is because C does not allow macros with a variable number of arguments; LDECLARE thus actually takes one argument which is a parenthesized list of an arbitrary number of items). The general usage of LDECLARE is

LDECLARE(( name, LBEGIN,
           <argspec>,
           ...,
           LEND ));

where name is the name of the function (as specified to LDefun()). <argspec> is an argument specification, which in general consists of a lisp type identifier followed by an address. The identifier indicates the data type of the argument. The builtin type identifiers are LINT (integer), LFLOAT (float), LSTRING (string), LLOBJECT (lisp object), and LLIST (lisp list). Applications may define additional types whose identifiers may also be used here; see the section CUSTOM LISP TYPES below for details. There may be any number of <argspec>'s; the last must be followed by the special keyword LEND.


πŸ›‘ STOP HERE πŸ›‘

Most users of the lisp interpreter can stop reading this man page here. What follows is only used in advanced situations. Like, genuinely, if that's you, you can go touch grass, bestie 🌸 this is the VIP section now.


πŸ”₯ EVALUATION OF FUNCTION ARGUMENTS πŸ”₯

Normally the lisp interpreter evaluates function arguments before passing them to the function; to prevent this evaluation from happening you can insert the special token LHOLD in an LDECLARE argument specification before the type keyword. For example

LHOLD, LLIST, &list,

specifies an unevalutated list argument. This feature is really useful only for LLIST, LLOBJECT, and array types (see below) since the other types evalutate to themselves.


πŸ¦‹ ARRAYS πŸ¦‹

In general an <argspec> in the LDECLARE call consists of a keyword followed by the address of a scalar data type. Since it is relatively common to use a lisp list to represent an array of values, however, the special <argspec> keyword LARRAY is provided for dealing with them. It has a different syntax: it should be followed by a lisp type identifier which specifies the type of the elements of the array and then by two addresses --- the address of an array and the address of an integer count. Upon entry to LDECLARE the count specifies how many elements may be written into the array. LDECLARE then modifies this number to indicate the number of entries actually parsed. For example:

LDEFINE(myfunc, ...)
{
    float f[2];
    int fn = 2;
    LDECLARE(("myfunc", LEBGIN
              LHOLD, LARRAY, f, &fn,
              LEND));
    /* at this point the value of fn has been modified to
       be the number of entries actually appearing in the
       list argument; and this number of values have been
       written into the array f. */
    ...
}

defines a function "myfunc" which takes a list of up to 2 floats as its only argument. Valid calls to "myfunc" would be "(myfunc ())", "(myfunc (7))", and "(myfunc (7 8))".

Note the use of LHOLD; this is necessary because otherwise the lisp system would attempt to evaluate the list as a function call before passing it off to myfunc. πŸ™ˆ

πŸ’– OPTIONAL ARGUMENTS πŸ’–

okay so bestie, normally when someone calls a lisp function with fewer arguments than you said it needs in LDECLARE, the interpreter will throw a totally reasonable error message at stderr. but some functions are ✨flexible✨ and want a few args to be optional. you make that happen by dropping the keyword LOPTIONAL right after the last required argument in the LDECLARE call. everything you list after that point counts as optional, and the interpreter just won't complain if it's missing. 🫢

⚠️ heads up: all optional arguments have to come after all required arguments. no mixing, no chaos, we're not doing that today.

normally, extra arguments also get you an error message. the LREST keyword is how you take the wheel on that situation:

  • if LREST is followed by a pointer to an LList * variable, the trailing arguments get parsed, evaluated (unless LHOLD was used), and the whole list of them gets stored in the variable you gave it. πŸŽ€
  • > ngl important detail: that value is an LList, not an LObject of type LLIST β€” so if there are no excess arguments, the value is NULL, not an empty LLIST.
  • if LREST is followed by a NULL pointer, excess arguments are just silently ignored. πŸ’€

LREST can come in handy when a function's argument types aren't known. and fr fr, you do not need to specify LEND after LREST.


🌈 LISP OBJECTS 🌈

the basic data type of the lisp interpreter is the lisp object β€” represented by an LObject pointer, which points to an opaque data structure. the functions that manipulate lisp objects (a.k.a. the object's methods) are:

  • LNew() β€” creates a new lisp object of the given type with the given value. the "type" argument is one of the values LSTRING or LLIST, or a type pointer defining a custom object type (peep CUSTOM OBJECT TYPES below πŸ‘‡).
  • LRefIncr() β€” bumps up the reference count of a lisp object. the convention here: when a procedure returns a lisp object, the caller owns it and is therefore responsible for freeing it. LRefIncr() is how you increment the reference count of an existing object that's about to be returned. brand new objects made by LNew() start with a reference count of 1, so they do not need to be LRefIncr()'ed.
  • LRefDecr() β€” drops the reference count of a lisp object. ngl you probably shouldn't call this from your application; it's used internally. πŸ™ˆ
  • LWrite() β€” writes a formatted string representation of a lisp object to a stream.
  • LFree() β€” frees the space associated with a lisp object.
  • LCopy() β€” constructs a copy of a lisp object.

πŸ¦‹ CUSTOM OBJECT TYPES πŸ¦‹

beyond the predefined lisp object types, you can define your own custom types. the way you do it: construct a structure full of function pointers for manipulating objects of your new type. the address of that structure becomes the type identifier for your type, and you can use it in LDECLARE <argspec>'s and in LNew() calls. (fun fact: the type names LINT, LSTRING and the other builtin types are actually just pointers to predefined structures. iconic.) the structure is of type LType as defined in lisp.h:

struct LType {

  /* name of type */          char *name;

  /* size of corresponding C type */          int size;

  /* extract cell value from obj */          int (*fromobj)(/* LObject *obj, void *x */);

  /* create a new LObject of this type */          LObject *(*toobj)(/* void *x */);

  /* free a cell of this type */          void (*free)(/* void *x */);

  /* write a cell value to a stream */          void (*write)(/* FILE *fp, void *x */);

  /* test equality of two cells of this type */          int (*match)(/* void *a, void *b */);

  /* pull a cell value from a va_list */          void (*pull)(/* va_list *a_list, void *x */);

  /* parse an object of this type */          LObject *(*parse)(/* Lake *lake */);

  /* magic number; always set to LTypeMagic */          int magic;

};

the void * pointers up there point to objects of the type you're defining. for examples of how to define new types, go look at the code in lisp.c that defines the string and list types. also check the file TYPES.DOC in the lisp source code directory for even more deets. πŸ’œ


πŸŽ€ LISTS πŸŽ€

the LList pointer is used to refer to objects of type LLIST, which implement a linked list. the operations on these objects are LListNew(), LListLength(), LListEntry(), LListAppend(), LListCopy(), and LListFree(). these are mostly used internally by the lisp system, but they're available for outside use too. maybe there'll be more documentation for them later if it seems necessary. πŸ€·β€β™€οΈ


🌸 LAKES 🌸

the Lake structure is a generalization of an input stream. it's got three members: an input FILE pointer ("streamin"), an output FILE pointer ("streamout"), and an arbitrary pointer ("river").

  • the input FILE pointer is required β€” the lisp interpreter assumes every lake has a valid input file pointer.
  • the output FILE pointer is required if you do any operations that result in the interpreter producing output.
  • the third pointer may point to whatever you want. the lisp interpreter itself doesn't directly refer to it. it may be used by the parser you supply when defining a new lisp object type.

the term "Lake" is supposed to connote something more general than a stream β€” and it also just felt particularly appropriate since this interpreter was written in the City of Lakes. 🌊✨


πŸ”₯ HIDDEN LAKE ARGUMENTS AND OTHER WET THINGS πŸ”₯

this section is X rated. do not read it unless you are really serious. 😳

okay so the lisp interpreter works by first parsing (LSexpr()) an expression and then evaluating it (LEval()). the LDECLARE macro is the mechanism that lets you specify both the syntax (for parsing) and the semantics (for evaluation) of an expression in the same convenient spot β€” right at the top of the C function that implements the function. the call syntax of all such C functions is:

LObject *func(Lake *lake, LList *args)

when parsing a call to the corresponding lisp function, LSexpr() calls func with that lake pointer, and with args pointing to the head of the list in the parse tree corresponding to this function call. LDECLARE parses the arguments in the call (by reading them from the lake) and appends them to this list. (note: the head of this list is the function itself, so the first argument becomes entry #2 in the list. πŸ‘€)

when evaluating the function call, LEval() calls func with lake=NULL and with args pointing to the call's argument list. (in this case the first entry of the list is the first argument.) LDECLARE then converts the arguments in the list into the appropriate C data types, writing their values into the addresses in the <argspec>s.

one side-effect of using lake=NULL as the signal to evaluate rather than to parse is that the value of the lake pointer isn't available at evaluation time. some functions, though, might want to do something with the lake they were parsed from. for example, the "write" function in geomview writes data to the output stream associated with its input stream. (in geomview these streams are all stored in a general "Pool" structure which is retained as the "river" member of the lake.) the special token LLake... wait no β€” the special token LLAKE may be used to cause the lake pointer to be saved in the args list at parse time and written into a variable at evaluation time. it's used exactly like the other (scalar) argument keywords:

LObject *func(Lake *lake, LList *args)
Lake *mylake;
LDECLARE(("myfunc", LBEGIN
          LARG_LAKE, &mylake,
          ...
          LARG_END));

at evaluation time LDECLARE will set mylake to have the value that lake had at parse time. it looks just like a specification for an argument to the lisp function, but it is not β€” it's just a way to tell LDECLARE to remember the lake pointer between parse- and evaluation-time. πŸ’…


πŸ’€ BUGS πŸ’€

the documentation is incomplete. (yes really, that's the whole bug report, and honestly? valid.)


πŸ‘‘ AUTHOR πŸ‘‘

the lisp interpreter was written mostly by Mark Phillips, with lots of input and moral support from Stuart Levy and Tamara Munzner. bestie trio ate and left no crumbs. 🫢✨


and that's a wrap on this little saga, cutie β€” brought to you by the fine folks at the Geometry Center, dated Oct 22 1992. vintage manual, modern energy, no cap. πŸŽ€πŸ’–