Added doc/capi.md

This commit is contained in:
rxi 2019-04-11 19:10:36 +01:00
parent eec1b62f5b
commit 9569f9309b
2 changed files with 125 additions and 0 deletions

View File

@ -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)**

124
doc/capi.md Normal file
View File

@ -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.