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.
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; };
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 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);
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. */
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.
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
).
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));
tail
flagStutter 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! */
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 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);