Add basic lexenv support

This commit is contained in:
2026-01-28 16:07:48 -08:00
parent 76b28c1dc0
commit 22ffac9321
12 changed files with 141 additions and 37 deletions

View File

@ -3,6 +3,7 @@
#include "gc.h" #include "gc.h"
#include "hashtable.h" #include "hashtable.h"
#include "lisp.h" #include "lisp.h"
#include "list.h"
#include "stack.h" #include "stack.h"
#include <stdio.h> #include <stdio.h>
@ -75,7 +76,7 @@ DEFUN(id, "id", (LispVal * obj), "(id)", "") {
} }
DEFUN(eq, "eq", (LispVal * obj1, LispVal *obj2), "(obj1 obj2)", "") { DEFUN(eq, "eq", (LispVal * obj1, LispVal *obj2), "(obj1 obj2)", "") {
return obj1 == obj2 ? Qt : Qnil; return EQ(obj1, obj2) ? Qt : Qnil;
} }
DEFSPECIAL(quote, "quote", (LispVal * form), "(form)", "") { DEFSPECIAL(quote, "quote", (LispVal * form), "(form)", "") {

View File

@ -282,6 +282,10 @@ static ALWAYS_INLINE bool NILP(LispVal *val) {
return val == Qnil; return val == Qnil;
} }
static ALWAYS_INLINE bool EQ(LispVal *val1, LispVal *val2) {
return val1 == val2;
}
// Some core functions // Some core functions
DECLARE_FUNCTION(id, (LispVal * obj)); DECLARE_FUNCTION(id, (LispVal * obj));
DECLARE_FUNCTION(eq, (LispVal * obj1, LispVal *obj2)); DECLARE_FUNCTION(eq, (LispVal * obj1, LispVal *obj2));

View File

@ -106,7 +106,7 @@ void parse_lambda_list(LambdaListParseResult *result, LispVal *list) {
out->kw = Qnil; out->kw = Qnil;
out->rest = Qnil; out->rest = Qnil;
size_t cur_idx = 0; // for keyword args size_t cur_idx = 0; // for keyword args
FOREACH_TAIL(list, tail) { DOTAILS(tail, list) {
if (!LISTP(tail)) { if (!LISTP(tail)) {
RETURN_ERROR(LLPS_DOTTED, list); RETURN_ERROR(LLPS_DOTTED, list);
} else if (out->allow_other_keys) { } else if (out->allow_other_keys) {
@ -178,7 +178,7 @@ void parse_lambda_list(LambdaListParseResult *result, LispVal *list) {
} }
#undef RETURN_ERROR #undef RETURN_ERROR
LispVal *make_builtin_function(LispVal *name, LispVal *(*cfunc)(), LispVal *make_builtin_function(LispVal *name, LispVal *(*cfunc)(void),
const char *lisp_args, size_t args_len, const char *lisp_args, size_t args_len,
LispVal *docstr) { LispVal *docstr) {
LispFunction *obj = lisp_alloc_object(sizeof(LispFunction), TYPE_FUNCTION); LispFunction *obj = lisp_alloc_object(sizeof(LispFunction), TYPE_FUNCTION);
@ -230,11 +230,11 @@ call_simple_native(LispVal *orig_func, LispFunction *fobj, LispVal *args) {
} }
LispVal *arg_arr[MAX_NATIVE_FUNCTION_ARGS]; LispVal *arg_arr[MAX_NATIVE_FUNCTION_ARGS];
size_t acount = 0; size_t acount = 0;
FOREACH(args, arg) { DOLIST(arg, args) {
if (fobj->flags.no_eval_args) { if (fobj->flags.no_eval_args) {
arg_arr[acount] = arg; arg_arr[acount] = arg;
} else { } else {
arg_arr[acount] = Feval(arg); arg_arr[acount] = Feval(arg, PARENT_LEXENV());
} }
add_local_reference(arg_arr[acount++]); add_local_reference(arg_arr[acount++]);
} }
@ -361,7 +361,7 @@ call_complex_native(LispVal *orig_func, LispFunction *fobj, LispVal *args) {
if (!arg_arr[i]) { if (!arg_arr[i]) {
arg_arr[i] = Qnil; arg_arr[i] = Qnil;
} else if (!fobj->flags.no_eval_args) { } else if (!fobj->flags.no_eval_args) {
arg_arr[i] = Feval(arg_arr[i]); arg_arr[i] = Feval(arg_arr[i], PARENT_LEXENV());
} }
add_local_reference(arg_arr[i]); add_local_reference(arg_arr[i]);
} }

View File

@ -2,7 +2,6 @@
#define INCLUDED_FUNCTION_H #define INCLUDED_FUNCTION_H
#include "base.h" #include "base.h"
#include "lisp_string.h"
DECLARE_SYMBOL(and_optional); DECLARE_SYMBOL(and_optional);
DECLARE_SYMBOL(and_rest); DECLARE_SYMBOL(and_rest);

View File

@ -56,7 +56,7 @@ static uintptr_t hash_key_for_table(LispHashTable *ht, LispVal *key) {
static bool compare_keys(LispHashTable *ht, LispVal *key1, LispVal *key2) { static bool compare_keys(LispHashTable *ht, LispVal *key1, LispVal *key2) {
if (NILP(ht->eq_fn) || ht->eq_fn == Qeq) { if (NILP(ht->eq_fn) || ht->eq_fn == Qeq) {
return key1 == key2; return EQ(key1, key2);
} else if (ht->eq_fn == Qstrings_equal) { // needed for initialization } else if (ht->eq_fn == Qstrings_equal) { // needed for initialization
return !NILP(Fstrings_equal(key1, key2)); return !NILP(Fstrings_equal(key1, key2));
} }

View File

@ -57,7 +57,23 @@ void lisp_shutdown(void) {
lisp_teardown_stack(); lisp_teardown_stack();
} }
DEFUN(eval, "eval", (LispVal * form), "(form)", "") { static inline LispVal *lookup_variable(LispSymbol *name, LispVal *lexenv) {
LispVal *lexval = Fplist_get(lexenv, name, Qunbound);
if (lexval != Qunbound) {
return lexval;
}
if (name->value == Qunbound) {
// TODO better error
printf("Unbound symbol: ");
debug_print(stdout, name);
fputc('\n', stdout);
abort();
}
return name->value;
}
DEFUN(eval, "eval", (LispVal * form, LispVal *lexenv),
"(form &optional lexenv)", "") {
if (!OBJECTP(form)) { if (!OBJECTP(form)) {
// fixnum or float // fixnum or float
return form; return form;
@ -75,21 +91,12 @@ DEFUN(eval, "eval", (LispVal * form), "(form)", "") {
out_data[i] = Qnil; out_data[i] = Qnil;
} }
for (size_t i = 0; i < vec->length; ++i) { for (size_t i = 0; i < vec->length; ++i) {
out_data[i] = Feval(vec->data[i]); out_data[i] = Feval(vec->data[i], lexenv);
} }
return newvec; return newvec;
} }
case TYPE_SYMBOL: { case TYPE_SYMBOL:
// TODO local bindings return lookup_variable(form, lexenv);
LispSymbol *sym = form;
if (sym->value == Qunbound) {
printf("Unbound symbol: ");
debug_print(stdout, form);
fputc('\n', stdout);
abort();
}
return sym->value;
}
case TYPE_CONS: { case TYPE_CONS: {
return Ffuncall(XCAR(form), XCDR(form)); return Ffuncall(XCAR(form), XCDR(form));
} }
@ -131,7 +138,7 @@ void debug_print(FILE *file, LispVal *obj) {
} }
case TYPE_CONS: { case TYPE_CONS: {
fputc('(', file); fputc('(', file);
FOREACH_TAIL(obj, tail) { DOTAILS(tail, obj) {
if (CONSP(tail)) { if (CONSP(tail)) {
debug_print(file, XCAR(tail)); debug_print(file, XCAR(tail));
if (!NILP(XCDR(tail))) { if (!NILP(XCDR(tail))) {
@ -162,6 +169,32 @@ void debug_print(FILE *file, LispVal *obj) {
} }
} }
DEFSPECIAL(progn, "progn", (LispVal * forms), "(&rest forms)", "") {
LispVal *rval = Qnil;
DOLIST(form, forms) {
rval = Feval(form, TOP_LEXENV());
}
return rval;
}
DEFSPECIAL(let, "let", (LispVal * bindings, LispVal *body),
"(bindings &rest body)", "") {
CHECK_LISTP(bindings);
copy_parent_lexenv();
DOLIST(binding, bindings) {
if (SYMBOLP(binding)) {
new_lexical_variable(binding, Qnil);
} else if (CONSP(binding) && list_length_eq(binding, 2)) {
new_lexical_variable(FIRST(binding),
Feval(SECOND(binding), TOP_LEXENV()));
} else {
// TODO better error
abort();
}
}
return Fprogn(body);
}
void debug_obj_info(FILE *file, LispVal *obj) { void debug_obj_info(FILE *file, LispVal *obj) {
fprintf(file, "%s -> ", LISP_TYPE_NAMES[TYPE_OF(obj)]); fprintf(file, "%s -> ", LISP_TYPE_NAMES[TYPE_OF(obj)]);
debug_print(file, obj); debug_print(file, obj);

View File

@ -16,7 +16,9 @@ void lisp_init(void);
void lisp_shutdown(void); void lisp_shutdown(void);
DECLARE_FUNCTION(eval, (LispVal * form)); DECLARE_FUNCTION(eval, (LispVal * form, LispVal *lexenv));
DECLARE_FUNCTION(progn, (LispVal * forms));
DECLARE_FUNCTION(let, (LispVal * bindings, LispVal *body));
__attribute__((no_sanitize("address"))) void debug_print(FILE *file, __attribute__((no_sanitize("address"))) void debug_print(FILE *file,
LispVal *obj); LispVal *obj);

View File

@ -60,3 +60,26 @@ DEFUN(listp, "listp", (LispVal * obj), "(obj)", "") {
DEFUN(list, "list", (LispVal * args), "(&rest args)", "") { DEFUN(list, "list", (LispVal * args), "(&rest args)", "") {
return args; return args;
} }
DEFUN(plist_put, "plist-put", (LispVal * plist, LispVal *prop, LispVal *value),
"(plist prop value)", "") {
CHECK_LISTP(plist);
DOTAILS(rest, plist) {
if (EQ(XCAR(rest), prop)) {
RPLACA(XCDR(rest), value);
return plist;
}
}
return CONS(prop, CONS(value, plist));
}
DEFUN(plist_get, "plist-get", (LispVal * plist, LispVal *prop, LispVal *def),
"(plist prop &optional default)", "") {
CHECK_LISTP(plist);
DOTAILS(rest, plist) {
if (EQ(XCAR(rest), prop)) {
return SECOND(rest);
}
}
return def;
}

View File

@ -99,11 +99,11 @@ static ALWAYS_INLINE LispVal *LIST_N(int count, ...) {
#define FOURTH(x) XCAR(XCDR(XCDR(XCDR(x)))) #define FOURTH(x) XCAR(XCDR(XCDR(XCDR(x))))
#define FIFTH(x) XCAR(XCDR(XCDR(XCDR(XCDR(x))))) #define FIFTH(x) XCAR(XCDR(XCDR(XCDR(XCDR(x)))))
#define FOREACH(l, v) \ #define DOLIST(v, l) \
for (LispVal *_tail = (l), *v = XCAR(_tail); !NILP(_tail); \ for (LispVal *_tail = (l), *v = XCAR(_tail); !NILP(_tail); \
_tail = XCDR(_tail), v = XCAR(_tail)) _tail = XCDR(_tail), v = XCAR(_tail))
#define FOREACH_TAIL(l, v) for (LispVal *v = (l); !NILP(v); v = XCDR_SAFE(v)) #define DOTAILS(v, l) for (LispVal *v = (l); !NILP(v); v = XCDR_SAFE(v))
// return -1 list is circular // return -1 list is circular
intptr_t list_length(LispVal *list); intptr_t list_length(LispVal *list);
@ -122,4 +122,7 @@ static ALWAYS_INLINE void CHECK_LISTP(LispVal *obj) {
} }
} }
DECLARE_FUNCTION(plist_put, (LispVal * plist, LispVal *prop, LispVal *value));
DECLARE_FUNCTION(plist_get, (LispVal * plist, LispVal *prop, LispVal *def));
#endif #endif

View File

@ -1,26 +1,20 @@
#include "lisp.h" #include "lisp.h"
#include "read.h" #include "read.h"
#include <stdio.h> DEFUN(print, "print", (LispVal * v), "(v)", "") {
debug_obj_info(stdout, v);
DEFUN(cool_func, "cool-func", (LispVal * a, LispVal *b), "(a &optional b)",
"") {
printf("A: ");
debug_obj_info(stdout, a);
printf("B: ");
debug_obj_info(stdout, b);
return Qnil; return Qnil;
} }
int main(int argc, const char **argv) { int main(int argc, const char **argv) {
lisp_init(); lisp_init();
REGISTER_GLOBAL_FUNCTION(cool_func); REGISTER_GLOBAL_FUNCTION(print);
push_stack_frame(Qnil, Qnil, Qnil); push_stack_frame(Qnil, Qnil, Qnil);
ReadStream s; ReadStream s;
const char BUF[] = "(cool-func 1 (cons 1 2))"; const char BUF[] = "(let ((a 1)) (print a))";
read_stream_init(&s, BUF, sizeof(BUF) - 1); read_stream_init(&s, BUF, sizeof(BUF) - 1);
LispVal *l = read(&s); LispVal *l = read(&s);
Feval(l); Feval(l, Qnil);
lisp_gc_now(NULL); lisp_gc_now(NULL);
pop_stack_frame(); pop_stack_frame();
lisp_shutdown(); lisp_shutdown();

View File

@ -161,7 +161,7 @@ static inline void add_local_refs_for_object_sub_vals(LispVal *seen_objs,
break; break;
} }
case TYPE_STRING: case TYPE_STRING:
// ho held refs // no held refs
break; break;
case TYPE_FIXNUM: case TYPE_FIXNUM:
case TYPE_FLOAT: case TYPE_FLOAT:
@ -190,3 +190,31 @@ void compact_stack_frame(struct StackFrame *restrict frame) {
lisp_realloc(refs->blocks, sizeof(struct LocalReferencesBlock *)); lisp_realloc(refs->blocks, sizeof(struct LocalReferencesBlock *));
refs->num_blocks = 1; refs->num_blocks = 1;
} }
bool set_lexical_variable(LispVal *name, LispVal *value,
bool create_if_absent) {
assert(the_stack.depth != 0);
DOTAILS(rest, LISP_STACK_TOP()->lexenv) {
if (EQ(XCAR(rest), name)) {
RPLACA(XCDR(rest), value);
return true;
}
}
if (create_if_absent) {
new_lexical_variable(name, value);
}
return create_if_absent;
}
void new_lexical_variable(LispVal *name, LispVal *value) {
assert(the_stack.depth != 0);
LISP_STACK_TOP()->lexenv =
CONS(name, CONS(value, LISP_STACK_TOP()->lexenv));
}
void copy_parent_lexenv(void) {
assert(the_stack.depth != 0);
if (the_stack.depth > 1) {
LISP_STACK_TOP()->lexenv = the_stack.frames[the_stack.depth - 2].lexenv;
}
}

View File

@ -40,6 +40,15 @@ static ALWAYS_INLINE struct StackFrame *LISP_STACK_TOP(void) {
return the_stack.depth ? &the_stack.frames[the_stack.depth - 1] : NULL; return the_stack.depth ? &the_stack.frames[the_stack.depth - 1] : NULL;
} }
static ALWAYS_INLINE LispVal *TOP_LEXENV(void) {
return the_stack.depth ? LISP_STACK_TOP()->lexenv : Qnil;
}
static ALWAYS_INLINE LispVal *PARENT_LEXENV(void) {
return the_stack.depth > 1 ? the_stack.frames[the_stack.depth - 2].lexenv
: Qnil;
}
void lisp_init_stack(void); void lisp_init_stack(void);
void lisp_teardown_stack(void); void lisp_teardown_stack(void);
void push_stack_frame(LispVal *name, LispVal *fobj, LispVal *args); void push_stack_frame(LispVal *name, LispVal *fobj, LispVal *args);
@ -47,6 +56,14 @@ void pop_stack_frame(void);
void add_local_reference_no_recurse(LispVal *obj); void add_local_reference_no_recurse(LispVal *obj);
void add_local_reference(LispVal *obj); void add_local_reference(LispVal *obj);
// Return true if successful, false if not found and not created
bool set_lexical_variable(LispVal *name, LispVal *value, bool create_if_absent);
// Just add a new lexical variable without any checking
void new_lexical_variable(LispVal *name, LispVal *value);
// Copy the previous frame's lexenv to the top of the stack.
void copy_parent_lexenv(void);
// used by the GC // used by the GC
void compact_stack_frame(struct StackFrame *restrict frame); void compact_stack_frame(struct StackFrame *restrict frame);