Skip to content

Instantly share code, notes, and snippets.

@dragoncoder047
Created January 20, 2024 20:15
Show Gist options
  • Save dragoncoder047/38aef7e4145f1dbb241547e9c7cec058 to your computer and use it in GitHub Desktop.
Save dragoncoder047/38aef7e4145f1dbb241547e9c7cec058 to your computer and use it in GitHub Desktop.
[incomplete/uncompileable] Proposal for the next version of uLisp (http://ulisp.com)
typedef const struct typetbl_entry typetbl_entry_t;
typedef const struct streamtbl_entry streamtbl_entry_t;
typedef struct sobject {
union {
struct {
sobject* car;
sobject* cdr;
};
struct {
typetbl_entry_t* type;
union {
symbol_t name;
streamtbl_entry_t* stream;
int integer;
int chars; // For strings
float single_float;
void* pointer; // For extensions, yum
};
};
};
} object;
typedef const struct {
const char* const string;
const fn_ptr_type fptr;
const minmax_t minmax;
const char* const doc;
} tbl_entry_t;
struct typetbl_entry {
const char* const typename;
// This function is called whenever the object `to_mark` needs to be marked.
// The object it returns is also marked, to optimize tail recursion.
object* (*markfun_t)(object* to_mark);
// This function is called when the object has no pointers to it, is not marked, and is about to be freed.
// If the function marks the object before it returns, the object won't be freed.
void (*freefun_t)(object* being_freed);
// This object is called when the object is supposed to be printed. It can inspect the PRINTREADABLY flag as needed.
void (*printfun_t)(object* to_print, object* stream);
};
struct streamtbl_entry {
const char* const prefix;
// If the stream is buffered by uLisp or not.
// If false, (unget-char) will error when called on this stream.
bool buffered;
// Called when the stream is opened in a (with-X) block or via (open)
// -- path is a pointer to the path after the prefix,
// e.g for (open "/sd/foo.txt" :mode :write-append) if the prefix is "/sd" the path will be "/foo.txt"
// -- args is the "mode" arguments passed to open function
// e.g for (open "/dev/i2c" :address #x70 :mode :read :if-does-not-exist nil)
// args will be (:address #x70 :mode :read :if-does-not-exist nil)
object* (*openfun)(const char* const path, object* args);
// Called to get one character from the stream when the buffer is empty.
char (*readfun)(object* stream);
// called to write one character to the stream.
void (*writefun)(object* stream, char to_write);
// Called when the (with-X) block exits or (close) is called on the stream.
void (*closefun)(object* stream);
};
/*
The actual in-memory uLisp stream object looks like this:
stream object
|
v
+---------------------+----+ +-------+-------+
| pointer to "stream" | | | | | <--------the cdr pointer is set to NIL
| typetbl_entry_t | o--------->| o | o | after the stream is closed
+---------------------+----+ +---|---+---|---+
| '-----------------------.
v |
+--------+---------------------+ |
| NULL | pointer to relevant | |
| | streamtbl_entry_t | |
+--------+---------------------+ |
|
.-----------------------------------------------------------------------'
|
| the cdr pointer is NIL
| if the stream is not read-buffered
| |
| v
| +---------+-------+ +-------------------------------+-------+ +-----------------+-----------------+
| | | | | t if last printed is \n | | | pointer to head | pointer to tail |
'-->| o | o--------->| nil if last printed is not \n | o---------->| of read buffer | of read buffer |
+----|----+-------+ +-------------------------------+-------+ +-----------------+-----------------+
| ^
| |
| the car value is used to implement (fresh-line)
| and the format code ~&
v
+----------+------------------------+
| | pointer to |
| NULL | implementation-defined | <---- openfun returns this cell, passed to closefun
| | cookie object |
+----------+------------------------+
The streams would be protected and automatically closed when GC'ed by making sure that the stream typed object is GC'ed first,
and then the rest of the stream payload. This is accomplished by the core extension's before_gc_hook function
which here, would find all live stream objects and mark their payloads (but not the object cell themselves) so to make sure
the stream can be closed properly before the payload is destroyed by the garbage collector.
To determine whether a cell is a cons or a special type, check if the car and cdr pointers point to valid Workspace memory locations.
If the car is out-of-bounds, but is not NULL, it is assumed it points to a typetbl_entry_t. If the car is NULL and the cdr
is out-of-bounds it is assumed it is just a "wild pointer" that means nothing to uLisp and is likely from an extension.
Wild pointers would print out like <pointer:0x00df54a8> or something like that.
*/
inline bool ptr_in_bounds (object* obj) {
return &Workspace[0] <= obj && obj < &Workspace[WORKSPACESIZE];
}
bool consp (object* obj) {
if (obj == NULL) return false;
if (car(obj) == NULL || ptr_in_bounds(car(obj)))
return cdr(obj) == NULL || ptr_in_bounds(cdr(obj));
return false;
}
bool wildpointerp (object* obj) {
return obj != NULL && car(obj) == NULL && (!ptr_in_bounds(cdr(obj)));
}
#define WITH_SZ(tbl) tbl, (sizeof(tbl) / sizeof(tbl[0]))
typedef struct {
const char* const ext_name; // this is provided so code can test if an extension is loaded
tbl_entry_t* const functions;
size_t num_functions;
streamtbl_entry_t* const streams;
size_t num_streams;
void (*before_gc_hook)(object* env); // Used to mark global/immortal objects used by the extension (e.g. Thrown in my catch/throw extension)
} extension_t;
extension_t Extensions[MAX_EXTENSIONS] = {
{"core", WITH_SZ(BuiltinsTable), WITH_SZ(BuiltinStreams), builtin_gc_cleanup},
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment