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 stt_obj_t structure. The type is stored in the type element. Possible values are defined in an enum in types.h. Here are the major types:

stt_obj_t *obj;

obj->type ==     desc            data storage

STT_ST_NIL           nil             No data.
   stt_create_nil(); /* return cached nil */
   stt_create_new_nil(); /* create new nil */

STT_ST_T             T (true)        No data.
   stt_create_t(); /* return cached T */
   stt_create_new_t(); /* create new T */

STT_ST_CONS          CONS pair       stt_obj_t * obj->d.cons.car
                                 stt_obj_t * obj->d.cons.cdr
   stt_create_cons(stt_obj_t *car, stt_obj_t *cdr);

STT_ST_NUM           float           double obj->d.num.value
   stt_create_num(double value);

STT_ST_INT           integer         int obj->d.integer.value
   stt_create_int(int value);

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

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

STT_ST_BUILTIN       builtin func    stt_builtin_handler obj->d.builtin.handler
                                 void * obj->d.builtin.data
                                     /* user data passed to handler */
   stt_create_builtin(stt_builtin_handler handler);
   stt_create_builtin_with_data(stt_builtin_handler handler, void *data);

STT_ST_FUNC          user function   stt_obj_t * obj->d.func.parm /* paramlist */
                                 stt_obj_t * obj->d.func.exp /* func body */
                                 int obj->d.func.macro /* macro flag */
								 stt_obj_t * obj->d.func.closure
   stt_create_func(stt_obj_t *prm, stt_obj_t *exp);

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

STT_ST_ERROR         error           int obj->d.error.code
                                 stt_obj_t * obj->d.error.obj /* thrown obj */
                                 char * obj->d.error.msg
                                 char * obj->d.error.file /* location */
                                 int obj->d.error.line
   stt_create_error(const int code, const char *message);
 stt_obj_t *stt_create_error_with_obj(const int code, const char *message, stt_obj_t *obj);

STT_ST_ARRAY         array           int obj->d.array.dim /* dimensions */
                                 int obj->d.array.size[STT_ARRAY_MAXDIMS];
                                        /* size of dimensions */
                                 stt_obj_t ** obj->d.array.data; /* data */
   stt_create_array(const int dim, const int *size);

STT_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
                                 stt_dictentry_t * obj->d.dict.data
   stt_create_dict();

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

STT_ST_STREAM        stream          int type; /* STREAM_{FP,FUNC} */
                                 union {
                                     FILE *fp;    /* STT_STREAM_FP */
                                     stt_obj_t *func; /* STT_STREAM_FUNC */
                                 } s;
   stt_create_stream(const int type);

   Describes an I/O stream.
   STT_STREAM_FP: holds a FILE * to read/write; NULL if closed already.
   STT_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).

STT_ST_COOKIE        cookie          int type;
                                 void *ptr;
   stt_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*:

stt_obj_t * stt_printobj(stt_varctx_t *ctx, const stt_obj_t *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 */
	stt_obj_t *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;
stt_obj_t **addr = ar->d.array.data;
int pos[STT_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 stt_dictput(stt_obj_t *dict, unsigned long keyhash, stt_obj_t *key, stt_obj_t *value);

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

/* get hash value for an object */
unsigned long stt_hashfunc(const stt_obj_t *obj);

Executing Stutter code

All Stutter functions are executed in a variable context (this will usually mean a stt_varctx_t * 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).

stt_varctx_t *global;

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

global = stt_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:

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

These all return the result of the last expression evaluated (or an STT_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:

stt_obj_t *eval(stt_varctx_t *ctx, stt_obj_t *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:

stt_varctx_free(global);              /* destroy the global stt_varctx_t */

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 stt_builtin_handler prototype. These functions look like this:

stt_obj_t *handler(stt_varctx_t *ctx, stt_obj_t *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 STT_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 stt_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(stt_obj_t **obj, stt_obj_t **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(stt_varctx_t *ctx, stt_obj_t **obj, stt_obj_t **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:

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

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

    while(nextarg(ctx, &obj, &parm)) {
        if(obj->type == STT_ST_ERROR) return obj;
        if(obj->type != STT_ST_INT)
            return stt_create_error(SE_TYPE, "Type mismatch");

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

    return stt_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):

stt_varctx_set(ctx, "MYFUNC", stt_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:

stt_obj_t *eval_(stt_varctx_t *ctx, stt_obj_t *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(stt_varctx_t *ctx, stt_obj_t **obj, stt_obj_t **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:

stt_obj_t *res;
res = eval(ctx,
    stt_create_cons(stt_create_symbol("fact"),
        stt_create_cons(stt_create_int(5), stt_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:

stt_obj_t *quoteobj(stt_obj_t *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 = stt_gc_prot_mark(); /* mark the place in the GC protection stack */

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

stt_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 stt_var_t struct:

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

	stt_obj_t *ref; /* stt_varctx_t 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 stt_varctx_t*_s function variants for speedy variable access.

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

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

/* get symbol name by symbol id */
char * stt_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 stt_varctx_store(stt_varctx_t *ctx, const char *name, stt_obj_t *val);
void stt_varctx_store_sdbm(stt_varctx_t *ctx, const char *name, stt_obj_t *val, unsigned long sdbmv);
void stt_varctx_store_s(stt_varctx_t *ctx, unsigned long sdbmv, unsigned long id, stt_obj_t *val);

/* get variable from a ctx (local only)
 * returns NULL if variable doesn't exist */
stt_var_t *stt_varctx_retr(stt_varctx_t *ctx, char *name);
stt_var_t *stt_varctx_retr_sdbm(stt_varctx_t *ctx, char *name, unsigned long sdbmv);
stt_var_t *stt_varctx_retr_s(stt_varctx_t *ctx, unsigned long sdbmv, unsigned long id);

/* get variable from a ctx (with parent fallback)
 * returns NULL if variable doesn't exist */
stt_var_t *stt_varctx_get(stt_varctx_t *ctx, char *name);
stt_var_t *stt_varctx_get_sdbm(stt_varctx_t *ctx, char *name, unsigned long sdbmv);
stt_var_t *stt_varctx_get_s(stt_varctx_t *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 stt_varctx_set(stt_varctx_t *ctx, char *name, stt_obj_t *val);
void stt_varctx_set_sdbm(stt_varctx_t *ctx, char *name, stt_obj_t *val, unsigned long sdbmv);
void stt_varctx_set_s(stt_varctx_t *ctx, unsigned long sdbmv, unsigned long id, stt_obj_t *val);

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