From 9569f9309b953fc08898d2560fe4461fbc7a1c64 Mon Sep 17 00:00:00 2001 From: rxi Date: Thu, 11 Apr 2019 19:10:36 +0100 Subject: [PATCH] Added doc/capi.md --- README.md | 1 + doc/capi.md | 124 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 125 insertions(+) create mode 100644 doc/capi.md diff --git a/README.md b/README.md index 1e8196f..8d458ab 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,7 @@ A *tiny*, embeddable language implemented in ANSI C --- * **[Demo Scripts](scripts)** +* **[C API Overview](doc/capi.md)** * **[Language Overview](doc/lang.md)** * **[Implementation Overview](doc/impl.md)** diff --git a/doc/capi.md b/doc/capi.md new file mode 100644 index 0000000..25df817 --- /dev/null +++ b/doc/capi.md @@ -0,0 +1,124 @@ + +# C API +To use `fe` in a project a `fe_Context` must first be intialized; +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. + +```c +int size = 1024 * 1024; +void *data = malloc(size); + +fe_Context *ctx = fe_open(data, size); + +/* ... */ + +fe_close(ctx); +free(data); +``` + + +## Running a script +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. + +```c +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); +``` + + +## Calling a function +A function can be called by creating a list and evaulating it; for +example, we could add two numbers using the `+` function: + +```c +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); +``` + + +## Creating a cfunc +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: + +```c +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: + +```clojure +(print (pow 2 10)) +``` + + +## Creating a ptr +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()`. + + +## Error handling +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.