Interfacing with Stutter

To use Stutter in your program, you have to link to the library and #include <stutter.h>, which will include further necessary headers. These functions aren't thread safe.

Basics

Stutter objects are all stored in a s_obj structure. The type is stored in the type element. Possible values are defined in an enum in types.h. Here are the major types:

s_obj *obj;

obj->type ==     desc            data storage

ST_NIL           nil             No data.
   create_nil(); /* return cached nil */
   create_new_nil(); /* create new nil */

ST_T             T (true)        No data.
   create_t(); /* return cached T */
   create_new_t(); /* create new T */

ST_CONS          CONS pair       s_obj * obj->d.cons.car
                                 s_obj * obj->d.cons.cdr
   create_cons(s_obj *car, s_obj *cdr);

ST_NUM           float           double obj->d.num.value
   create_num(double value);

ST_INT           integer         int obj->d.integer.value
   create_int(int value);

ST_STRING        string          char * obj->d.string.value
   create_string(const char *str); /* strdups the string */
   create_string_allocd(char *str); /* doesn't strdup the string..
                                       GC will free it! */

ST_SYMBOL        symbol          char * obj->d.symbol.name
                                 unsigned long sdbm /* name sdbm */
                                 unsigned long id /* unique ID */
                                 int colon /* keyword symbol flag */
   create_symbol(char *name); /* strdups the name */

ST_BUILTIN       builtin func    builtin_handler obj->d.builtin.handler
                                 void * obj->d.builtin.data
                                     /* user data passed to handler */
   create_builtin(builtin_handler handler);
   create_builtin_with_data(builtin_handler handler, void *data);

ST_FUNC          user function   s_obj * obj->d.func.parm /* paramlist */
                                 s_obj * obj->d.func.exp /* func body */
                                 int obj->d.func.macro /* macro flag */
								 s_obj * obj->d.func.closure
   create_func(s_obj *prm, s_obj *exp);

   If the closure object isn't NULL, the function is a closure;
   .closure will contain a pointer to an ST_VARCTX object.

ST_ERROR         error           int obj->d.error.code
                                 s_obj * obj->d.error.obj /* thrown obj */
                                 char * obj->d.error.msg
                                 char * obj->d.error.file /* location */
                                 int obj->d.error.line
   create_error(const int code, const char *message);
 s_obj *create_error_with_obj(const int code, const char *message, s_obj *obj);

ST_ARRAY         array           int obj->d.array.dim /* dimensions */
                                 int obj->d.array.size[ARRAY_MAXDIMS];
                                        /* size of dimensions */
                                 s_obj ** obj->d.array.data; /* data */
   create_array(const int dim, const int *size);

ST_DICT          dictionary      unsigned int obj->d.dict.size
                                 unsigned int obj->d.dict.mask /* size-1 */
                                 unsigned int obj->d.dict.fill
                                 unsgined int obj->d.dict.threshold
                                 dictentry * obj->d.dict.data
   create_dict();

ST_VARCTX        encapsulated variable context
                                 varctx * obj->d.varctx.ctx
   This is an object encapsulating a variable context;
   it is used with closures.

ST_STREAM        stream          int type; /* STREAM_{FP,FUNC} */
                                 union {
                                     FILE *fp;    /* STREAM_FP */
                                     s_obj *func; /* STREAM_FUNC */
                                 } s;
   create_stream(const int type);

   Describes an I/O stream.
   STREAM_FP: holds a FILE * to read/write; NULL if closed already.
   STREAM_FUNC: the func has to be a lambda; is called for each write
    with a character; is called for each read (has to return a char).

ST_COOKIE        cookie          int type;
                                 void *ptr;
   create_cookie(const int type, void *ptr);

   This object encapsulates a pointer. The language won't support any
   operations on it except storing/passing. Can be used for e.g. database
   handles.

To print a representation of an object to *output-stream*:

s_obj * printobj(varctx *ctx, const s_obj *obj);

Object property lists are stored in the obj->props element. These are simple linked lists.

typedef struct objprop objprop;
struct objprop {
	unsigned long key;   /* key symbol id */
	s_obj *value;        /* value */
	objprop *next;
};

Array representation

All pointers of the array are stored continuously, in one big chunk. To calculate the address of an element from indices, you can do this:

int ds = 1, i;
s_obj **addr = ar->d.array.data;
int pos[ARRAY_MAXDIMS] = { ... }
for(i=0; i<ar->d.array.dim; i++) {
	addr += (ds * pos[i]);
	ds *= ar->d.array.size[i];
}

Dictionaries

Dictionaries are dinamically grown hashes. They can be accessed with the following functions:

/* put value into dictionary */
void dictput(s_obj *dict, unsigned long keyhash, s_obj *key, s_obj *value);

/* get value from dictionary */
/* returns NULL if key is not found */
s_obj *dictget(const s_obj *dict, const s_obj *key);

/* get hash value for an object */
unsigned long hashfunc(const s_obj *obj);

Executing Stutter code

All Stutter functions are executed in a variable context (this will usually mean a varctx * for your C program). Variable contexts hold name->object bindings and a pointer to the parent variable context (such as from the function that called this function, or NULL if we're in the global context).

First you need to create the global variable context and initialise it with the builtins (and later, your own functions).

varctx *global;

stutter_init();           /* initialize Stutter */
atexit(stutter_shutdown); /* let Stutter clean up its internal structures */

global = varctx_create(NULL, 1024); /* NULL is the parent context (none).
                                       The number means the number of slots
                                       in the name->object hash.
                                       Must be a power of two! */
register_builtins(global);          /* Bind variables to builtins. */

There are various ways to parse&run code. Here's a list:

s_obj *evalfile(varctx *ctx, const char *name);   /* parse a file */
s_obj *evalfp(varctx *ctx, FILE *fp);             /* parse from a FILE * */

These all return the result of the last expression evaluated (or an ST_ERROR if something bad happened).

Since all Stutter code is composed of lists, which is a valid datatype itself, there's a basic function to evaluate an object:

s_obj *eval(varctx *ctx, s_obj *obj);

All parsing functions use this one, after having the text converted into lists.

After executing code, you might want to process the returned object. Logically, the garbage collector cannot free this object automatically. After you're done with it (copied/used the value, or just don't need it any more), you have to tell the GC to "forget" the protected objects:

gc_prot_free(0, NULL);

When your business with Stutter is complete, you have to clean up:

varctx_free(global);              /* destroy the global varctx */

You can control the behavior of the garbage collector by adjusting two global variables:

int GC_VERBOSE;
 /* set this to 1 if you want the GC to report to stderr */
int GC_ALLOC_LIMIT;
 /* if the number of newly allocated objects since the last garbage collection
  * exceeds this value, the GC is activated. Adjust this variable to change
  * the frequency of automatic GC. */

Getting called

C functions that Stutter code can call are of the builtin_handler prototype. These functions look like this:

s_obj *handler(varctx *ctx, s_obj *parm, void *data, const int tail);

ctx is the variable context the function is called from;
parm is the list of parameters the function received;
data is the user data that is specified in the ST_BUILTIN object (NULL if none).
tail is a flag; it's 1 if your function was called in a tail position. (More on this later.)

Warning: all functions must return a valid object. NULL absolutely cannot be returned, use create_nil() instead.

Function arguments

parm is a Stutter list: that is, a CONS that's CDR might contain another CONS, and so on; or nil, if the function was called without parameters. It contains the parameters unevaluated. This means if the call was like (myfunc 1 2 (+ 2 3)), the list is: (1 2 (+ 2 3)) - two integers and a list. If your function needs the values of the parameters, not the form (this is probably the case, most of the time), then it needs to evaluate them. There are convenience functions to do this without too much hassle.

int nextparm(s_obj **obj, s_obj **parm);
 /* Gets the parameter in *parm and places it in *obj.
  * Advances *parm to the next element ((*parm)->d.cons.cdr).
  * Returns 1 on success, 0 if the list is now empty. */

int nextarg(varctx *ctx, s_obj **obj, s_obj **parm);
 /* Behaves exactly like nextparm, except that it evaluates
  * the parameter before placing it in *obj (by calling eval(ctx, *obj)). */

If an evaluation returns an error, it's important to pass it on (return it) immediately, unless under some special circumstances (such as in the builtin function catch).

Example

A simple function to return the greatest integer from its parameters might look like this:

s_obj *maxint(varctx *ctx, s_obj *parm, void *data, const int tail)
{
    int val;
    s_obj *obj;

    if(!nextarg(ctx, &obj, &parm))
        return create_error(SE_PNUM, "Insufficient parameters for maxint");
    if(obj->type == ST_ERROR) return obj;
    if(obj->type != ST_INT)
        return create_error(SE_TYPE, "Type mismatch");
    val = obj->d.integer.value;

    while(nextarg(ctx, &obj, &parm)) {
        if(obj->type == ST_ERROR) return obj;
        if(obj->type != ST_INT)
            return create_error(SE_TYPE, "Type mismatch");

        if(obj->d.integer.value > val)
            val = obj->d.integer.value;
    }

    return create_int(val);
}

If you want your Stutter code to be able to call this function, you need to bind it to a name in a variable context somewhere (probably the global one):

varctx_set(ctx, "MYFUNC", create_builtin(maxint));

The tail flag

Stutter can optimise tail recursion into iteration. This goes like this: if in a user function another function is called in a place where its value would be returned directly without further modification (such as the last expression in the function body), the call is said to be in a tail position. To execute user functions that are in tail position, the interpreter doesn't need to recurse; it can reuse the local context (since the data of the old function won't be used any more). This is faster and doesn't use up the stack.

For you, this means that if your function gets arguments that it might evaluate and return the result unchanged (such as the branches of the if builtin), and it gets 1 in the tail flag, it should use a special form of eval for evaluation, to let the interpreter know that optimisation might be possible. If you don't do this, nothing bad will happen, except that possible optimisations aren't performed.

The special form of eval is simple:

s_obj *eval_(varctx *ctx, s_obj *obj, const int tail);

This is exactly like eval, except for the extra tail argument. In fact, eval is a macro for eval_(ctx, obj, 0). You should pass 1 as tail if you intend to return the result of the evaluation unchanged, and you got 1 the tail parameter of your builtin function.

There is a variation of nextarg to make this easier in some situations:

int nextargtail(varctx *ctx, s_obj **obj, s_obj **parm, const int tail);
/* get and evaluate next parameter (returns 1 on success, 0 if there are no more).
 * if it's the last parameter and tail is true, calls eval
 * with tail flag set to 1! */

Calling Stutter functions

If you need to call a user-defined function, the simplest way is to create an S-expression and give it to eval. For example, this function might be defined:

(set 'fact (lambda (x)
    (if (< x 1)
        1
        (* x (fact (- x 1))))))

Calling this function with a parameter would look like (fact 5) in Stutter. This is a list: (fact . (5 . nil)), where fact is a symbol. In C, you can construct and execute this structure:

s_obj *res;
res = eval(ctx,
    create_cons(create_symbol("fact"),
        create_cons(create_int(5), create_nil())));

You might have previously looked up the function object directly, you can pass that instead of creating a symbol. Remember to check for errors in the result.

If you need to pass parameters that aren't just simple numeric atoms or strings, you must remember that the list you pass to eval goes through evaluation. You can't just pass a list to a function like this:

(reverse (1 2 3))

... Since (1 2 3) will be understood as an S-expression. Instead, you'd do:

(reverse '(1 2 3))

Your C code has to pass the list (reverse (quote (1 2 3))) to eval. To simplify this task, there's a helper function:

s_obj *quoteobj(s_obj *obj);
 /* Wraps obj in (quote): that is, returns the list (quote obj). */

As it was said before, the garbage collector can't automatically free function return values. If you call a Stutter function from the outside (i.e. not from a builtin), you'll need to dispose of the result object yourself (or more like, let the GC dispose of it). Here's the way:

s_mark mark;

mark = gc_prot_mark(); /* mark the place in the GC protection stack */

res = eval( ... );
/* process the result */

gc_prot_free(mark, NULL); /* pop everything from the stack until the mark */

Variable context API

Variable contexts consist of a hash, each slot of which stores a linked list of the s_var struct:

typedef struct s_var s_var;
struct s_var {
    char *name;   /* variable name (NULL from 0.7 on!) */
	unsigned long sdbm; /* name sdbm */
	unsigned long id; /* symbol id */
    s_obj *value; /* value */
    s_var *next;  /* next variable in this chain */

	s_obj *ref; /* varctx object that references this ctx */
	unsigned long int mark; /* used by GC */
	int defer; /* use a deferred hash (for closures) */
};

You can use the following functions to handle variables yourself. The system converts each symbol into a (per name) unique ID on parsing to accelerate variable lookups. You have to use the varctx*_s function variants for speedy variable access.

/* create variable context. Size must be a power of two.  */
varctx *varctx_create(varctx *parent, const int size);

/* get unique symbol id of a symbol */
unsigned long get_symbolid(char *name, unsigned long sdbmv);

/* get symbol name by symbol id */
char * get_symbolname(unsigned long sdbmv, unsigned long id);

/* store a variable inside a ctx
 * doesn't check for existence, don't use this
 * use varctx_set instead! */
void varctx_store(varctx *ctx, const char *name, s_obj *val);
void varctx_store_sdbm(varctx *ctx, const char *name, s_obj *val, unsigned long sdbmv);
void varctx_store_s(varctx *ctx, unsigned long sdbmv, unsigned long id, s_obj *val);

/* get variable from a ctx (local only)
 * returns NULL if variable doesn't exist */
s_var *varctx_retr(varctx *ctx, char *name);
s_var *varctx_retr_sdbm(varctx *ctx, char *name, unsigned long sdbmv);
s_var *varctx_retr_s(varctx *ctx, unsigned long sdbmv, unsigned long id);

/* get variable from a ctx (with parent fallback)
 * returns NULL if variable doesn't exist */
s_var *varctx_get(varctx *ctx, char *name);
s_var *varctx_get_sdbm(varctx *ctx, char *name, unsigned long sdbmv);
s_var *varctx_get_s(varctx *ctx, unsigned long sdbmv, unsigned long id);

/* set/store a variable inside a ctx
 * (updates if the var already exists)
 * note that this doesn't automatically capitalize the name, but all Stutter
 * symbols are all capitals - if you give lowercase characters, your
 * variable can never be resolved */
void varctx_set(varctx *ctx, char *name, s_obj *val);
void varctx_set_sdbm(varctx *ctx, char *name, s_obj *val, unsigned long sdbmv);
void varctx_set_s(varctx *ctx, unsigned long sdbmv, unsigned long id, s_obj *val);

/* frees varctx.
 * do not free a varctx if it has ->ref != NULL; this means that a varctx
 * encapsulator object refers to this ctx (and as such, this ctx will be freed
 * by the GC. */
void varctx_free(varctx *ctx);