Started work on binding functions. Not yet complete.

This commit is contained in:
Simon Brooke 2026-05-04 16:15:57 +01:00
parent f4303247b9
commit efa6a3246d
17 changed files with 321 additions and 41 deletions

View file

@ -0,0 +1,175 @@
/**
* environment/function_bindings.c
*
* Post Scarcity Software Environment:
*
* Provide bindings for substrate functions. At least in theory, these
* bindings only need to be initialised on node zero.
* todo: they really ought to be in a namespace ::system:bootstrap, once I
* have namespaces and paths working.
*
* (c) 2026 Simon Brooke <simon@journeyman.cc>
* Licensed under GPL version 2.0, or, at your option, any later version.
*/
#include <stdbool.h>
#include <wchar.h>
#include "environment/privileged_keywords.h"
#include "memory/node.h"
#include "memory/pointer.h"
#include "memory/tags.h"
#include "ops/assoc.h"
#include "ops/bind.h"
#include "ops/cond.h"
#include "ops/eval_apply.h"
#include "ops/eq.h"
#include "ops/inspect.h"
#include "ops/stack_ops.h"
#include "ops/string_ops.h"
#include "payloads/cons.h"
#include "payloads/function.h"
#include "payloads/special.h"
/**
* Bind this compiled `executable` function, as a Lisp function, to
* this name in the `oblist`.
* \todo where a function is not compiled from source, we could cache
* the name on the source pointer. Would make stack frames potentially
* more readable and aid debugging generally.
*/
struct pso_pointer
bind_function(struct pso_pointer frame_pointer, wchar_t *name, wchar_t *doc,
struct pso_pointer (*executable)(struct pso_pointer)) {
struct pso_pointer result = fetch_env(frame_pointer);
struct pso_pointer n = c_string_to_lisp_symbol(frame_pointer, name);
struct pso_pointer d = c_string_to_lisp_string(frame_pointer, doc);
struct pso_pointer meta =
make_cons( frame_pointer, make_cons( frame_pointer, privileged_keyword_bootstrap, nil),
make_cons( frame_pointer, make_cons( frame_pointer, privileged_keyword_name, n),
make_cons( frame_pointer, make_cons( frame_pointer, privileged_keyword_documentation, d), nil)));
struct pso_pointer r = make_function(frame_pointer, meta, executable);
if (!exceptionp(r)) {
result = make_cons( frame_pointer, make_cons( frame_pointer, n, r), result);
}
return result;
}
/**
* Bind this compiled `executable` function, as a Lisp special form, to
* this `name` in the `oblist`.
*/
struct pso_pointer
bind_special(struct pso_pointer frame_pointer, wchar_t *name, wchar_t *doc,
struct pso_pointer (*executable)(struct pso_pointer)) {
struct pso_pointer result = fetch_env(frame_pointer);
struct pso_pointer n = c_string_to_lisp_symbol(frame_pointer, name);
struct pso_pointer d = c_string_to_lisp_string(frame_pointer, doc);
struct pso_pointer meta =
make_cons( frame_pointer, make_cons( frame_pointer, privileged_keyword_bootstrap, nil),
make_cons( frame_pointer, make_cons( frame_pointer, privileged_keyword_name, n),
make_cons( frame_pointer, make_cons( frame_pointer, privileged_keyword_documentation, d), nil)));
struct pso_pointer r = make_special(frame_pointer, meta, executable);
if (!exceptionp(r)) {
result = make_cons( frame_pointer, make_cons( frame_pointer, n, r), result);
}
return result;
}
struct function_data {
char32_t *name;
char32_t *documentation;
void* executable;
};
/* right, the problem with all those pretty '#ifdefs' which might allow us to
* simply switch functions on and off just by including or not including .h
* files is that the C compiler is too primitive to know how many items there
* are in an array. So this number must be edited manually, and must be right.
*/
#define N_FUNCTION_INITIALISERS 4
/** initialisers for functions */
struct function_data function_initialisers[] = {
#ifdef __psse_ops_assoc_h
{U"assoc",
U"(assoc key store): search `store` for the value associated with "
U"`key`.",
&assoc},
#endif
#ifdef __psse_ops_bind_h
{U"bind!",
U"(bind! key value store): bind `key` to `value` in this store, modifying "
U"the store if it is writable to the user, otherwise returning a new "
U"store",
&bind},
#endif
#ifdef __psse_ops_eq_h
{U"eq",
U"(eq args...): shallow, cheap equality; returns `t` if all `args...` "
U"are the same object, else `nil`.",
&eq},
{U"equal",
U"(equal a b): expensive, deep equality: returns `t` if objects `a` "
U"and `b` have recursively equal value.",
&equal},
#endif
#ifdef __psse_ops_eval_apply_h
// TODO: there's a lot of other stuff in eval_apply.c, which ought to be in
// other files but at present isn't.
{U"apply",
U"(apply fn args...): apply this `fn` to these `args...` and return "
U"their value.",
&lisp_apply},
{U"eval",
U"(eval expression): evaluate this `expression` and return its value",
&lisp_eval},
#endif
#ifdef __psse_ops_inspect_h
{
U"inspect",
U"(inspect expr), (inspect expr write-stream): inspect one complete "
U"lisp expression and return `nil`. If `write-stream` is specified and "
U"is a write stream, then print to that stream, else to the stream "
U"which is the value of `*out*` in the environment.",
&lisp_inspect
},
#endif
};
/* right, the problem with all those pretty '#ifdefs' which might allow us to
* simply switch functions on and off just by including or not including .h
* files is that the C compiler is too primitive to know how many items there
* are in an array */
#define N_SPECIAL_INITIALISERS 1
/** initialisers for special forms */
struct function_data special_initialisers[] = {
#ifdef __psse_ops_cond_h
{U"cond",
U"(cond clauses...): conditional. Each `clause` is expected to be a "
U"list; if the first item in such a list evaluates to non-nil, the "
U"remaining items in that list are evaluated in turn and the value of "
U"the last returned. If no arg `clause` has a first element which "
U"evaluates to non nil, then nil is returned",
&lisp_cond},
#endif
};
struct pso_pointer
initialise_function_bindings(struct pso_pointer frame_pointer) {
struct pso_pointer result = fetch_env(frame_pointer);
return result;
}