To use fe
in a project a fe_Context
must first be initialized;
this is done by using the fe_open()
function. The function expects a
block of memory (typically greater than 16kb), the block is used by the
context to store objects and context state and should remain valid for
the lifetime of the context. fe_close()
should be called when you are
finished with a context, this will assure any ptr
objects are properly
garbage collected.
int size = 1024 * 1024;
void *data = malloc(size);
fe_Context *ctx = fe_open(data, size);
/* ... */
fe_close(ctx);
free(data);
To run a script it should first be read then evaluated; this should be
done in a loop if there are several root-level expressions contained in
the script. fe_readfp()
is provided as a convenience to read from a
file pointer; fe_read()
can be used with a custom fe_ReadFn
callback
function to read from other sources.
FILE *fp = fopen("test.fe", "rb");
int gc = fe_savegc(ctx);
for (;;) {
fe_Object *obj = fe_readfp(ctx, fp);
/* break if there's nothing left to read */
if (!obj) { break; }
/* evaluate read object */
fe_eval(ctx, obj);
/* restore GC stack which would now contain both the read object and
** result from evaluation */
fe_restoregc(ctx, gc);
}
fclose(fp);
A function can be called by creating a list and evaulating it; for
example, we could add two numbers using the +
function:
int gc = fe_savegc(ctx);
fe_Object *objs[3];
objs[0] = fe_symbol(ctx, "+");
objs[1] = fe_number(ctx, 10);
objs[2] = fe_number(ctx, 20);
fe_Object *res = fe_eval(ctx, fe_list(ctx, objs, 3));
printf("result: %g\n", fe_tonumber(ctx, res));
/* discard all temporary objects pushed to the GC stack */
fe_restoregc(ctx, gc);
A cfunc
can be created by using the fe_cfunc()
function with a
fe_CFunc
function argument. The cfunc
can be bound to a global
variable by using the fe_set()
function. cfunc
s take a context and
argument list as its arguments and returns a result object. The result
should never be NULL
; in the case of wanting to return nil
the value
returned by fe_bool(ctx, 0)
should be used.
The pow
function from math.h
could be wrapped as such:
static fe_Object* f_pow(fe_Context *ctx, fe_Object *arg) {
float x = fe_tonumber(ctx, fe_nextarg(ctx, &arg));
float y = fe_tonumber(ctx, fe_nextarg(ctx, &arg));
return fe_number(ctx, pow(x, y));
}
fe_set(ctx, fe_symbol(ctx, "pow"), fe_cfunc(ctx, f_pow));
The cfunc
could then be called like any other function:
(print (pow 2 10))
The ptr
object type is provided to allow for custom objects. By default
no type checking is performed and thus pointers must be wrapped by the
user and tagged to assure type safety if more than one type of pointer
is used.
A ptr
object can be created by using the fe_ptr()
function.
The gc
and mark
handlers are provided for dealing with ptr
s
regarding garbage collection. Whenever a ptr
is marked by the GC the
mark
handler is called on it — this is useful if the ptr
stores
additional objects which also need to be marked via fe_mark()
. The
gc
handler is called on the ptr
when it becomes unreachable and is
garbage collected, such that the resources used by the ptr
can be
freed. The handlers can be set by setting the relevant fields in the
struct returned by fe_handlers()
.
When an error occurs the fe_error()
is called; by default, the
error and stack traceback is printed and the program exited. If you want
to recover from an error the error
handler field in the struct returned
by fe_handlers()
can be set and longjmp()
can be used to exit the
handler; the context is left in a safe state and can continue to be
used. New fe_Object
s should not be created inside the error handler.