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