#include "lisp.h" // used by static function registering macros #include "read.h" // IWYU pragma: keep #include #include #include #include #include // TODO switch to stdio #include // used to fix up some indentation or syntax highlighting problems #define IGNORE() struct __ignored_struct LispVal *LISP_TYPE_SYMS[N_LISP_TYPES] = {NULL}; // ####################### // # nil, unbound, and t # // ####################### DEF_STATIC_STRING(_Qnil_name, "nil"); LispSymbol _Qnil = { .type = TYPE_SYMBOL, .name = &_Qnil_name, .package = Qnil, .plist = Qnil, .function = Qnil, .value = Qnil, .value_doc = Qnil, .is_const_value = true, .is_const_func = false, .is_special_var = true, }; DEF_STATIC_STRING(_Qunbound_name, "unbound"); LispSymbol _Qunbound = { .type = TYPE_SYMBOL, .name = &_Qunbound_name, .package = Qnil, .plist = Qnil, .function = Qnil, .value = Qunbound, .value_doc = Qnil, .is_const_value = true, .is_const_func = true, .is_special_var = true, }; DEF_STATIC_STRING(_Qt_name, "t"); LispSymbol _Qt = { .type = TYPE_SYMBOL, .name = &_Qt_name, .package = Qnil, .plist = Qnil, .function = Qnil, .value = Qt, .value_doc = Qnil, .is_const_value = true, .is_const_func = true, .is_special_var = true, }; // ########################### // # Other important symbols # // ########################### DEF_STATIC_SYMBOL(backquote, "`"); DEF_STATIC_SYMBOL(comma, ","); DEF_STATIC_SYMBOL(comma_at, ",@"); DEF_STATIC_SYMBOL(macro, "macro"); DEF_STATIC_SYMBOL(opt, "&opt"); DEF_STATIC_SYMBOL(key, "&key"); DEF_STATIC_SYMBOL(allow_other_keys, "&allow-other-keys"); DEF_STATIC_SYMBOL(rest, "&rest"); DEF_STATIC_SYMBOL(declare, "declare"); DEF_STATIC_SYMBOL(name, "name"); DEF_STATIC_SYMBOL(no_backtrace, "no-backtrace"); DEF_STATIC_SYMBOL(symbol, "symbol"); DEF_STATIC_SYMBOL(integer, "integer"); DEF_STATIC_SYMBOL(float, "float"); DEF_STATIC_SYMBOL(function, "function"); DEF_STATIC_SYMBOL(hash_table, "hash_table"); DEF_STATIC_SYMBOL(user_pointer, "user_pointer"); DEF_STATIC_SYMBOL(package, "package"); DEF_STATIC_SYMBOL(record, "record"); // ############################ // # Global Package Variables # // ############################ LispVal *package_table = Qnil; LispVal *system_package = Qnil; LispVal *keyword_package = Qnil; LispVal *current_package = Qnil; // ############################# // # Allocation and references # // ############################# static size_t bytes_allocated = 0; static size_t last_gc = 0; void *lisp_malloc(size_t size) { return lisp_realloc(NULL, size); } void *lisp_realloc(void *old_ptr, size_t size) { if (!size) { return NULL; } if (refcount_default_context && !refcount_is_doing_gc()) { bytes_allocated += size; } void *new_ptr = realloc(old_ptr, size); if (!new_ptr) { abort(); } return new_ptr; } void *lisp_malloc0(size_t size) { void *ptr = lisp_malloc(size); if (ptr && size) { memset(ptr, 0, size); } return ptr; } void garbage_collect(void) { last_gc = bytes_allocated; refcount_garbage_collect(); } static bool held_refs_callback(void *obj, RefcountList **held, void *ignored) { switch (TYPEOF(obj)) { case TYPE_STRING: case TYPE_INTEGER: case TYPE_FLOAT: case TYPE_USER_POINTER: // no held refs return true; case TYPE_SYMBOL: *held = refcount_list_push(*held, ((LispSymbol *) obj)->name); *held = refcount_list_push(*held, ((LispSymbol *) obj)->function); *held = refcount_list_push(*held, ((LispSymbol *) obj)->plist); *held = refcount_list_push(*held, ((LispSymbol *) obj)->value); *held = refcount_list_push(*held, ((LispSymbol *) obj)->value_doc); return true; case TYPE_PAIR: *held = refcount_list_push(*held, ((LispPair *) obj)->head); *held = refcount_list_push(*held, ((LispPair *) obj)->tail); return true; case TYPE_VECTOR: { LispVector *vec = obj; for (size_t i = 0; i < vec->length; ++i) { *held = refcount_list_push(*held, vec->data[i]); } return true; } case TYPE_HASHTABLE: { LispHashtable *ht = obj; HT_FOREACH_VALID_INDEX(obj, i) { *held = refcount_list_push(*held, HASH_KEY(obj, i)); *held = refcount_list_push(*held, HASH_VALUE(obj, i)); } if (ht->eq_fn != Qstrings_equal) { *held = refcount_list_push(*held, ht->eq_fn); } if (ht->hash_fn != Qhash_string) { *held = refcount_list_push(*held, ht->hash_fn); } return true; } case TYPE_FUNCTION: { LispFunction *fn = obj; *held = refcount_list_push(*held, fn->name); *held = refcount_list_push(*held, fn->props); *held = refcount_list_push(*held, fn->return_tag); *held = refcount_list_push(*held, fn->args); *held = refcount_list_push(*held, fn->kwargs); *held = refcount_list_push(*held, fn->oargs); *held = refcount_list_push(*held, fn->rargs); *held = refcount_list_push(*held, fn->lexenv); *held = refcount_list_push(*held, fn->doc); *held = refcount_list_push(*held, fn->rest_arg); if (!fn->is_builtin) { *held = refcount_list_push(*held, fn->body); } return true; } case TYPE_PACKAGE: { LispPackage *pkg = obj; *held = refcount_list_push(*held, pkg->name); *held = refcount_list_push(*held, pkg->imported); *held = refcount_list_push(*held, pkg->obarray); *held = refcount_list_push(*held, pkg->exported_sym_table); return true; } case TYPE_RECORD: { LispRecord *rec = obj; *held = refcount_list_push(*held, rec->record_type); for (size_t i = 0; i < rec->length; ++i) { *held = refcount_list_push(*held, rec->data[i]); } return true; } default: abort(); } } static void free_obj_callback(void *obj, void *ignored) { switch (TYPEOF(obj)) { case TYPE_STRING: { LispString *str = obj; if (!str->is_static) { lisp_free(str->data); } } break; case TYPE_VECTOR: { LispVector *vec = obj; if (!vec->is_static) { lisp_free(vec->data); } } break; case TYPE_USER_POINTER: { LispUserPointer *ptr = obj; if (ptr->free_func) { ptr->free_func(ptr->data); } } break; case TYPE_HASHTABLE: { LispHashtable *ht = obj; lisp_free(ht->key_vals); } break; case TYPE_FUNCTION: case TYPE_SYMBOL: case TYPE_PAIR: case TYPE_INTEGER: case TYPE_FLOAT: case TYPE_PACKAGE: case TYPE_RECORD: // no internal data to free break; default: abort(); } lisp_free(obj); } // ################ // # Constructors # // ################ #define CONSTRUCT_OBJECT(var, Type, TYPE) \ Type *var = lisp_malloc(sizeof(Type)); \ refcount_init_obj(var); \ var->type = TYPE; LispVal *make_lisp_string(const char *data, size_t length, bool take, bool is_static) { CONSTRUCT_OBJECT(self, LispString, TYPE_STRING); if (take) { self->data = (char *) data; } else { self->data = lisp_malloc(length + 1); memcpy(self->data, data, length); self->data[length] = '\0'; } self->length = length; self->is_static = is_static; return LISPVAL(self); } LispVal *make_lisp_symbol(LispVal *name) { CHECK_TYPE(TYPE_STRING, name); CONSTRUCT_OBJECT(self, LispSymbol, TYPE_SYMBOL); self->name = (LispString *) refcount_ref(name); self->package = Qnil; self->plist = Qnil; self->function = Qnil; self->value = Qunbound; self->value_doc = Qnil; self->is_const_value = false; self->is_const_func = false; self->is_special_var = false; return LISPVAL(self); } LispVal *make_lisp_pair(LispVal *head, LispVal *tail) { CONSTRUCT_OBJECT(self, LispPair, TYPE_PAIR); self->head = refcount_ref(head); self->tail = refcount_ref(tail); return LISPVAL(self); } LispVal *make_lisp_integer(intmax_t value) { CONSTRUCT_OBJECT(self, LispInteger, TYPE_INTEGER); self->type = TYPE_INTEGER; self->value = value; return LISPVAL(self); } LispVal *make_lisp_float(long double value) { CONSTRUCT_OBJECT(self, LispFloat, TYPE_FLOAT); self->value = value; return LISPVAL(self); } LispVal *make_lisp_vector(LispVal **data, size_t length) { CONSTRUCT_OBJECT(self, LispVector, TYPE_VECTOR); self->data = data; self->length = length; self->is_static = false; return LISPVAL(self); } static bool parse_opt_arg_entry(LispVal *ent, struct OptArgDesc *aod, LispVal *found_args) { aod->name = Qnil; aod->default_form = Qnil; aod->pred_var = Qnil; if (TYPEOF(ent) == TYPE_SYMBOL) { if (VALUE_CONSTANTP(ent)) { return false; } else if (!NILP(gethash(found_args, ent, Qnil))) { return false; } aod->name = refcount_ref(ent); aod->pred_var = Qnil; aod->default_form = Qnil; return true; } else if (LISTP(ent) && SYMBOLP(HEAD(ent)) && !VALUE_CONSTANTP(HEAD(ent)) && LISTP(TAIL(ent))) { LispVal *end = TAIL(TAIL(ent)); if (!LISTP(end) || (!SYMBOLP(HEAD(end)) && !NILP(HEAD(end))) || (!NILP(HEAD(end)) && VALUE_CONSTANTP(HEAD(end)))) { return false; } else if (!NILP(gethash(found_args, HEAD(ent), Qnil))) { return false; } else if (!NILP(end) && (!NILP(gethash(found_args, HEAD(end), Qnil)) || VALUE_CONSTANTP(HEAD(end)) || HEAD(end) == HEAD(ent))) { return false; } aod->name = refcount_ref(HEAD(ent)); aod->default_form = refcount_ref(HEAD(TAIL(ent))); aod->pred_var = refcount_ref(HEAD(end)); return true; } return false; } LispVal *make_lisp_function(LispVal *name, LispVal *return_tag, LispVal *args, LispVal *lexenv, LispVal *body, LispVal *doc, bool is_macro, LispVal *props) { CONSTRUCT_OBJECT(self, LispFunction, TYPE_FUNCTION); self->is_builtin = false; self->is_macro = is_macro; self->args = Qnil; self->rargs = Qnil; self->oargs = Qnil; self->rest_arg = Qnil; self->kwargs = Qnil; self->name = Qnil; self->return_tag = Qnil; self->lexenv = Qnil; self->doc = Qnil; self->body = Qnil; void *cl = register_cleanup(&refcount_unref_as_callback, self); set_function_args(self, args); cancel_cleanup(cl); // do these after the potential throw self->name = refcount_ref(name); self->return_tag = refcount_ref(return_tag); self->lexenv = refcount_ref(lexenv); self->doc = refcount_ref(doc); self->body = refcount_ref(body); self->props = refcount_ref(props); return LISPVAL(self); } LispVal *make_lisp_hashtable(LispVal *eq_fn, LispVal *hash_fn) { CONSTRUCT_OBJECT(self, LispHashtable, TYPE_HASHTABLE); self->table_size = LISP_HASHTABLE_INITIAL_SIZE; self->key_vals = lisp_malloc0(sizeof(struct HashtableEntry) * self->table_size); self->count = 0; // needed during early initialization if (eq_fn == Qstrings_equal) { self->eq_fn = eq_fn; } else { self->eq_fn = refcount_ref(eq_fn); } if (hash_fn == Qhash_string) { self->hash_fn = hash_fn; } else { self->hash_fn = refcount_ref(hash_fn); } return LISPVAL(self); } LispVal *make_user_pointer(void *data, void (*free_func)(void *)) { CONSTRUCT_OBJECT(self, LispUserPointer, TYPE_USER_POINTER); self->data = data; self->free_func = free_func; return LISPVAL(self); } LispVal *make_lisp_package(LispVal *name) { CHECK_TYPE(TYPE_STRING, name); CONSTRUCT_OBJECT(self, LispPackage, TYPE_PACKAGE); self->name = refcount_ref(name); self->obarray = make_lisp_hashtable(Qstrings_equal, Qhash_string); self->exported_sym_table = make_lisp_hashtable(Qnil, Qnil); self->imported = Qnil; return LISPVAL(self); } LispVal *make_lisp_record(LispVal *type, size_t length) { CONSTRUCT_OBJECT(self, LispRecord, TYPE_RECORD); self->record_type = refcount_ref(type); self->function = Qnil; self->data = lisp_malloc(sizeof(LispVal *) * length); for (size_t i = 0; i < length; ++i) { self->data[i] = Qnil; } self->length = length; return LISPVAL(self); } LispVal *predicate_for_type(LispType type) { switch (type) { case TYPE_STRING: return Qstringp; case TYPE_SYMBOL: return Qsymbolp; case TYPE_PAIR: return Qpairp; case TYPE_INTEGER: return Qintegerp; case TYPE_FLOAT: return Qfloatp; case TYPE_VECTOR: return Qvectorp; case TYPE_FUNCTION: return Qfunctionp; case TYPE_HASHTABLE: return Qhash_table_p; case TYPE_USER_POINTER: return Quser_pointer_p; case TYPE_PACKAGE: return Qpackagep; case TYPE_RECORD: return Qrecordp; default: abort(); } } // ############################### // # Initialization and Shutdown # // ############################### static void register_symbols_and_functions(void); void lisp_init(void) { RefcountContext *ctx = refcount_make_context( offsetof(LispVal, refcount), Qnil, held_refs_callback, free_obj_callback, NULL, &(RefcountAllocator) {.malloc.no_data = lisp_malloc, .free.no_data = lisp_free}); refcount_default_context = ctx; REGISTER_SYMBOL_NOINTERN(unbound); REGISTER_SYMBOL_NOINTERN(nil); REGISTER_SYMBOL_NOINTERN(t); LISP_TYPE_SYMS[TYPE_STRING] = Qstring; LISP_TYPE_SYMS[TYPE_SYMBOL] = Qsymbol; LISP_TYPE_SYMS[TYPE_PAIR] = Qpair; LISP_TYPE_SYMS[TYPE_INTEGER] = Qinteger; LISP_TYPE_SYMS[TYPE_FLOAT] = Qfloat; LISP_TYPE_SYMS[TYPE_VECTOR] = Qvector; LISP_TYPE_SYMS[TYPE_FUNCTION] = Qfunction; LISP_TYPE_SYMS[TYPE_HASHTABLE] = Qhash_table; LISP_TYPE_SYMS[TYPE_USER_POINTER] = Quser_pointer; LISP_TYPE_SYMS[TYPE_PACKAGE] = Qpackage; LISP_TYPE_SYMS[TYPE_RECORD] = Qrecord; package_table = make_lisp_hashtable(Qstrings_equal, Qhash_string); LispVal *sys_package_name = STATIC_STRING("sys"); system_package = make_lisp_package(sys_package_name); refcount_unref(Fregister_package(system_package)); refcount_unref(sys_package_name); LispVal *kw_package_name = STATIC_STRING("kw"); keyword_package = make_lisp_package(kw_package_name); refcount_unref(Fregister_package(keyword_package)); refcount_unref(kw_package_name); current_package = refcount_ref(system_package); register_symbols_and_functions(); } void lisp_shutdown(void) { garbage_collect(); refcount_unref(current_package); refcount_unref(system_package); refcount_unref(package_table); refcount_context_destroy(refcount_default_context); refcount_default_context = NULL; } // ############################### // # General and Misc. Functions # // ############################### DEFUN(exit, "exit", (LispVal * code), "(&opt code)", "Exit with CODE, defaulting to zero.") { if (!NILP(code) && !INTEGERP(code)) { Fthrow(Qtype_error, Qnil); } Fthrow(Qshutdown_signal, const_list(true, 1, code)); } DEFUN(id, "id", (LispVal * obj), "(obj)", "Return a number identifying OBJ uniquely among all currently live " "objects.") { return make_lisp_integer((int64_t) obj); } DEFUN(eq, "eq", (LispVal * obj1, LispVal *obj2), "(obj1 obj2)", "Return non-nil if OBJ1 and OBJ2 are the same object. Objects which are " "eq also have the same id.") { return LISP_BOOL(obj1 == obj2); } static bool pairs_equal_internal(LispVal *obj1, LispVal *obj2) { while (PAIRP(obj1) && PAIRP(obj2) && !NILP(Fequal(HEAD(obj1), HEAD(obj2)))) { obj1 = TAIL(obj1); obj2 = TAIL(obj2); } return !PAIRP(obj1) && !NILP(Fequal(obj1, obj2)); } DEFUN(equal, "equal", (LispVal * obj1, LispVal *obj2), "(obj1 obj2)", "Return non-nil if OBJ1 and OBJ2 are structurally equal.") { if (obj1 == obj2) { return Qt; } else if (TYPEOF(obj1) != TYPEOF(obj2)) { return Qnil; } switch (TYPEOF(obj1)) { case TYPE_SYMBOL: case TYPE_FUNCTION: case TYPE_PACKAGE: // only if they are the same object (checked above) return Qnil; case TYPE_PAIR: return LISP_BOOL(pairs_equal_internal(obj1, obj2)); case TYPE_USER_POINTER: return LISP_BOOL(USERPTR(void *, obj1) == USERPTR(void *, obj2)); case TYPE_STRING: return Fstrings_equal(obj1, obj2); case TYPE_INTEGER: return LISP_BOOL(((LispInteger *) obj1)->value == ((LispInteger *) obj2)->value); case TYPE_FLOAT: return LISP_BOOL(((LispFloat *) obj1)->value == ((LispFloat *) obj2)->value); case TYPE_VECTOR: { LispVector *v1 = (LispVector *) obj1; LispVector *v2 = (LispVector *) obj2; if (v1->length != v2->length) { return Qnil; } for (size_t i = 0; i < v1->length; ++i) { if (!Fequal(v1->data[i], v2->data[i])) { return Qnil; } } return Qt; } case TYPE_HASHTABLE: { LispHashtable *t1 = (LispHashtable *) obj1; LispHashtable *t2 = (LispHashtable *) obj2; if (t1->count != t2->count || NILP(Fequal(t1->eq_fn, t2->eq_fn)) || NILP(Fequal(t1->hash_fn, t2->hash_fn))) { return Qnil; } HT_FOREACH_VALID_INDEX(t1, i) { if (NILP(Fequal(HASH_VALUE(t1, i), gethash(obj2, HASH_KEY(t1, i), Qunbound)))) { return Qnil; } } return Qt; } case TYPE_RECORD: { // TODO implement this return Qnil; } default: abort(); } } static void breakpoint(int64_t id) {} DEFUN(breakpoint, "breakpoint", (LispVal * id), "(&opt id)", "Do nothing.") { if (NILP(id)) { breakpoint(0); } else { CHECK_TYPE(TYPE_INTEGER, id); breakpoint(((LispInteger *) id)->value); } return Qnil; } DEFUN(not, "not", (LispVal * obj), "(obj)", "Return t if OBJ is nil, otherwise, return nil.") { return NILP(obj) ? Qt : Qnil; } DEFUN( type_of, "type-of", (LispVal * obj), "(obj)", "Return a symbol that describes the type of OBJ. This is for informational " "purposes only, don't use this to test for objects of a specific type.") { if (TYPEOF(obj) != TYPE_RECORD) { return refcount_ref(LISP_TYPE_SYMS[TYPEOF(obj)]); } else { return refcount_ref(((LispRecord *) obj)->record_type); } } DEFUN(user_pointer_p, "user-pointer-p", (LispVal * val), "(obj)", "Return non-nil if OBJ is a user pointer.") { return LISP_BOOL(USER_POINTER_P(val)); } DEFUN(callablep, "callablep", (LispVal * val), "(obj)", "Return non-nil if OBJ is callable.") { if (FUNCTIONP(val)) { return refcount_ref(val); } else if (SYMBOLP(val)) { return Fcallablep(Fsymbol_function(val, Qt)); } else if (PAIRP(val) && HEAD(val) == Qlambda) { return refcount_ref(val); } else if (CALLABLE_RECORD_P(val)) { return Fcallablep(((LispRecord *) val)->function); } return Qnil; } DEFUN(native_type_p, "native-type-p", (LispVal * sym), "(sym)", "Return non-nil if SYM names a native type.") { CHECK_TYPE(TYPE_SYMBOL, sym); for (size_t i = 0; i < N_LISP_TYPES; ++i) { if (!NILP(Feq(LISP_TYPE_SYMS[i], sym))) { return Qt; } } return Qnil; } // ################################## // # Evaluation and Macro Expansion # // ################################## static inline LispVal *eval_function_args(LispVal *args, LispVal *lexenv) { LispVal *final_args = Qnil; WITH_PUSH_FRAME(Qnil, Qnil, true, { void *cl_handle = register_cleanup( (lisp_cleanup_func_t) &unref_double_ptr, &final_args); LispVal *end = Qnil; FOREACH(elt, args) { if (NILP(final_args)) { final_args = Fpair(Feval(elt, lexenv), Qnil); refcount_unref(HEAD(final_args)); end = final_args; } else { LispVal *new_end = Fpair(Feval(elt, lexenv), Qnil); refcount_unref(HEAD(new_end)); Fsettail(end, new_end); refcount_unref(new_end); end = new_end; } } cancel_cleanup(cl_handle); }); return final_args; } static LispVal **process_builtin_args(LispVal *fname, LispFunction *func, LispVal *args, size_t *nargs) { size_t raw_count = (func->n_req + func->n_opt + ((LispHashtable *) func->kwargs)->count + !NILP(func->rest_arg)); *nargs = raw_count; LispVal **vec = lisp_malloc0(sizeof(LispVal *) * raw_count); LispVal *rest = Qnil; LispVal *rest_end = Qnil; size_t have_count = 0; LispVal *opt_desc; LispVal *arg = Qnil; // last arg processed while (!NILP(args)) { arg = HEAD(args); if (have_count < func->n_req + func->n_opt) { vec[have_count++] = refcount_ref(arg); } else if (KEYWORDP(arg) && !NILP(opt_desc = HEAD(gethash(func->kwargs, arg, Qnil))) && NILP(rest)) { struct OptArgDesc *oad = USERPTR(struct OptArgDesc, opt_desc); if (vec[oad->index]) { goto multikey; } args = TAIL(args); if (NILP(args)) { goto key_no_val; } vec[oad->index] = refcount_ref(HEAD(arg)); } else if (KEYWORDP(arg) && !func->allow_other_keys && NILP(func->rest_arg)) { goto unknown_key; } else if (NILP(func->rest_arg)) { goto too_many; } else if (NILP(rest)) { rest = Fpair(arg, Qnil); rest_end = rest; } else { LispVal *new_end = Fpair(arg, Qnil); Fsettail(rest_end, new_end); refcount_unref(new_end); rest_end = new_end; } args = TAIL(args); } if (have_count < func->n_req) { goto too_few; } if (!NILP(func->rest_arg)) { vec[raw_count - 1] = rest; } for (size_t i = 0; i < raw_count; ++i) { if (!vec[i]) { vec[i] = func->distinguish_unpassed ? Qunbound : Qnil; } } return vec; // TODO different messages key_no_val: too_many: multikey: unknown_key: too_few: refcount_unref(rest); for (size_t i = 0; i < raw_count; ++i) { refcount_unref(vec[i]); } lisp_free(vec); Fthrow(Qargument_error, Fpair(fname, Qnil)); return NULL; } static LispVal *call_builtin(LispVal *name, LispFunction *func, LispVal *args, LispVal *args_lexenv) { // builtin macros inherit their parents lexenv if (func->is_macro) { the_stack->lexenv = refcount_ref(args_lexenv); } size_t nargs; LispVal **arg_vec = process_builtin_args(name, func, args, &nargs); struct UnrefListData cleanup_data = {.vals = arg_vec, .len = nargs}; void *cl = register_cleanup(&unref_free_list_double_ptr, &cleanup_data); LispVal *retval; switch (nargs) { case 0: retval = ((LispVal * (*) (void) ) func->builtin)(); break; case 1: retval = ((LispVal * (*) (LispVal *) ) func->builtin)(arg_vec[0]); break; case 2: retval = ((LispVal * (*) (LispVal *, LispVal *) ) func->builtin)(arg_vec[0], arg_vec[1]); break; case 3: retval = ((LispVal * (*) (LispVal *, LispVal *, LispVal *) ) func->builtin)(arg_vec[0], arg_vec[1], arg_vec[2]); break; case 4: retval = ((LispVal * (*) (LispVal *, LispVal *, LispVal *, LispVal *) ) func->builtin)(arg_vec[0], arg_vec[1], arg_vec[2], arg_vec[3]); break; case 5: retval = ((LispVal * (*) (LispVal *, LispVal *, LispVal *, LispVal *, LispVal *) ) func->builtin)(arg_vec[0], arg_vec[1], arg_vec[2], arg_vec[3], arg_vec[4]); break; case 6: retval = ((LispVal * (*) (LispVal *, LispVal *, LispVal *, LispVal *, LispVal *, LispVal *) ) func->builtin)(arg_vec[0], arg_vec[1], arg_vec[2], arg_vec[3], arg_vec[4], arg_vec[5]); break; default: fprintf(stderr, "Builtin functions cannot have more than 6 arguments!\n"); abort(); } cancel_cleanup(cl); refcount_ref(retval); unref_free_list_double_ptr(&cleanup_data); return retval; } static void new_lexical_var(LispVal **lexenv, LispVal *name, LispVal *value) { if (SPECIALP(name)) { push_to_lexenv(&the_stack->dynenv, name, value); } else { push_to_lexenv(lexenv, name, value); } } static void process_lisp_args(LispVal *fname, LispFunction *func, LispVal *args, LispVal **lexenv) { LispVal *added_kwds = make_lisp_hashtable(Qnil, Qnil); void *cl_handle = register_cleanup(&refcount_unref_as_callback, added_kwds); enum { REQ, OPT, KEY, REST } mode = REQ; LispVal *rargs = func->rargs; LispVal *oargs = func->oargs; while (!NILP(args)) { LispVal *arg = HEAD(args); switch (mode) { case REQ: { if (NILP(rargs)) { mode = OPT; continue; // skip increment } new_lexical_var(lexenv, HEAD(rargs), arg); rargs = TAIL(rargs); } break; case OPT: { if (NILP(oargs)) { mode = KEY; continue; // skip increment } struct OptArgDesc *oad = USERPTR(struct OptArgDesc, HEAD(oargs)); new_lexical_var(lexenv, oad->name, arg); if (!NILP(oad->pred_var)) { new_lexical_var(lexenv, oad->pred_var, Qt); } oargs = TAIL(oargs); } break; case KEY: if (!KEYWORDP(arg)) { mode = REST; continue; // skip increment } LispVal *desc_lv = gethash(func->kwargs, arg, Qnil); if (NILP(desc_lv)) { if (!func->allow_other_keys) { goto unknown_key; } mode = REST; continue; // skip increment } struct OptArgDesc *oad = USERPTR(struct OptArgDesc, desc_lv); args = TAIL(args); if (NILP(args)) { goto missing_value; } LispVal *value = HEAD(args); puthash(added_kwds, oad->name, Qt); new_lexical_var(lexenv, oad->name, value); if (!NILP(oad->pred_var)) { new_lexical_var(lexenv, oad->pred_var, Qt); } break; case REST: if (NILP(func->rest_arg)) { if (KEYWORDP(arg)) { args = TAIL(args); if (NILP(args)) { goto missing_value; } args = TAIL(args); continue; // skip increment } else { goto too_many_args; } } new_lexical_var(lexenv, func->rest_arg, args); // done processing goto done_adding; } args = TAIL(args); } if (!NILP(rargs)) { goto missing_required; } HT_FOREACH_VALID_INDEX(func->kwargs, i) { struct OptArgDesc *oad = USERPTR(struct OptArgDesc, HASH_VALUE(func->kwargs, i)); // only check the current function's lexenv and not its parents' if (NILP(gethash(added_kwds, oad->name, Qnil))) { LispVal *eval_res = Feval(oad->default_form, the_stack->lexenv); new_lexical_var(lexenv, oad->name, eval_res); refcount_unref(eval_res); if (!NILP(oad->pred_var)) { new_lexical_var(lexenv, oad->pred_var, Qnil); } } } FOREACH(arg, oargs) { struct OptArgDesc *oad = USERPTR(struct OptArgDesc, arg); LispVal *default_val = Feval(oad->default_form, the_stack->lexenv); new_lexical_var(lexenv, oad->name, default_val); refcount_unref(default_val); if (!NILP(oad->pred_var)) { new_lexical_var(lexenv, oad->pred_var, Qnil); } } if (!NILP(func->rest_arg)) { new_lexical_var(lexenv, func->rest_arg, Qnil); } done_adding: cancel_cleanup(cl_handle); refcount_unref(added_kwds); return; // TODO different messages missing_required: too_many_args: missing_value: unknown_key: cancel_cleanup(cl_handle); refcount_unref(added_kwds); Fthrow(Qargument_error, Fpair(fname, Qnil)); } static LispVal *call_lisp_function(LispVal *name, LispFunction *func, LispVal *args, LispVal *args_lexenv) { the_stack->lexenv = refcount_ref(func->lexenv); process_lisp_args(name, func, args, &the_stack->lexenv); if (func->is_macro) { if (!the_stack->next) { abort(); } LispVal *expansion = Fprogn(func->body); LispVal *retval = Qnil; // disable internal handlers the_stack->enable_handlers = false; WITH_CLEANUP(expansion, { // eval in the outer lexenv retval = Feval(expansion, args_lexenv); }); the_stack->enable_handlers = true; // just in case return retval; } else { return Fprogn(func->body); } } STATIC_DEFUN(set_for_return, "set-for-return", (LispVal * entry, LispVal *dest), "(entry dest)", "Internal function.") { LispVal *retval = HEAD(TAIL(HEAD(entry))); Fsethead(dest, retval); return Qnil; } static inline void setup_return_handler(LispVal *tag, LispVal *dest) { LispVal *err_var = INTERN_STATIC("e", system_package); LispVal *quoted_dest = const_list(false, 2, Qquote, dest); LispVal *handler = const_list(true, 4, err_var, Qset_for_return, err_var, quoted_dest); refcount_unref(quoted_dest); puthash(the_stack->handlers, tag, handler); refcount_unref(handler); } static LispVal *call_function(LispVal *func, LispVal *args, LispVal *args_lexenv, bool eval_args, bool allow_macro) { LispFunction *fobj = (LispFunction *) Fcallablep(func); if (PAIRP(fobj)) { LispVal *real_fobj = Feval(LISPVAL(fobj), args_lexenv); refcount_unref(fobj); fobj = (LispFunction *) real_fobj; } void *cl_handle = register_cleanup(refcount_unref_as_callback, fobj); if (NILP(fobj)) { Fthrow(Qvoid_function_error, const_list(true, 1, func)); } else if (!FUNCTIONP(fobj)) { Fthrow(Qinvalid_function_error, Fpair(LISPVAL(fobj), Qnil)); } else if (!allow_macro && fobj->is_macro) { Fthrow(Qtype_error, Qnil); } if (!fobj->is_macro && eval_args) { args = eval_function_args(args, args_lexenv); } LispVal *retval = Qnil; LispVal *return_ptr = Fpair(Qnil, Qnil); void *return_cl_handle = register_cleanup(refcount_unref_as_callback, return_ptr); refcount_ref(args); WITH_CLEANUP(args, { WITH_PUSH_FRAME_NO_REF_HANDLING_THROWS( refcount_ref(fobj), refcount_ref(args), false, // make sure the lexenv is nil { the_stack->hidden = false; if (!NILP(fobj->return_tag)) { the_stack->return_tag = refcount_ref(fobj->return_tag); setup_return_handler(fobj->return_tag, return_ptr); } if (fobj->is_builtin) { retval = call_builtin(func, fobj, args, args_lexenv); } else { retval = call_lisp_function(func, fobj, args, args_lexenv); } }, { retval = refcount_ref(HEAD(return_ptr)); // }); }); cancel_cleanup(return_cl_handle); refcount_unref(return_ptr); cancel_cleanup(cl_handle); refcount_unref(fobj); return retval; } static inline LispVal *find_in_lexenv(LispVal *lexenv, LispVal *key) { return Fplist_get(lexenv, key, Qunbound, Qnil); } static inline LispVal *find_dynamic_value_on_stack(LispVal *key) { if (!the_stack) { return Qunbound; } return Fplist_get(the_stack->dynenv, key, Qunbound, Qnil); } static LispVal *symbol_value_in_lexenv(LispVal *lexenv, LispVal *key) { CHECK_TYPE(TYPE_SYMBOL, key); if (SPECIALP(key)) { LispVal *local = find_dynamic_value_on_stack(key); if (local != Qunbound) { return local; } } else if (!NILP(lexenv)) { LispVal *local = find_in_lexenv(lexenv, key); if (local != Qunbound) { return local; } } LispVal *sym_val = Fsymbol_value(key, Qt); if (sym_val != Qunbound) { return sym_val; } Fthrow(Qvoid_variable_error, const_list(true, 1, key)); } DEFUN(eval, "eval", (LispVal * form, LispVal *lexenv), "(eval &opt lexenv)", "Evaluate FORM in the lexical environment LEXENV.") { switch (TYPEOF(form)) { case TYPE_STRING: case TYPE_FUNCTION: case TYPE_INTEGER: case TYPE_FLOAT: case TYPE_HASHTABLE: case TYPE_USER_POINTER: case TYPE_PACKAGE: case TYPE_RECORD: // the above all are self-evaluating return refcount_ref(form); case TYPE_SYMBOL: if (KEYWORDP(form)) { return refcount_ref(form); } else { return symbol_value_in_lexenv(lexenv, form); } case TYPE_VECTOR: { LispVector *vec = (LispVector *) form; LispVal **elts = lisp_malloc0(sizeof(LispVal *) * vec->length); WITH_PUSH_FRAME(Qnil, Qnil, true, { struct UnrefListData uld; uld.vals = elts; uld.len = vec->length; void *cl_handler = register_cleanup(&unref_free_list_double_ptr, &uld); for (size_t i = 0; i < vec->length; ++i) { elts[i] = Feval(vec->data[i], lexenv); } cancel_cleanup(cl_handler); }); // does not ref its arguments return make_lisp_vector(elts, vec->length); } case TYPE_PAIR: { LispPair *pair = (LispPair *) form; return call_function(pair->head, pair->tail, lexenv, true, true); } default: abort(); } } DEFUN(funcall, "funcall", (LispVal * function, LispVal *rest), "(function &rest args)", "Call FUNCTION with ARGS as its arguments.") { return call_function(function, rest, Qnil, false, false); } DEFUN(apply, "apply", (LispVal * function, LispVal *rest), "(function &rest args)", "Call FUNCTION with ARGS as its arguments. If the last element of ARGS " "is a list, use its elements as arguments as well.") { LispVal *args = Qnil; LispVal *end = Qnil; while (!NILP(rest) && !NILP(((LispPair *) rest)->tail)) { if (NILP(args)) { args = Fpair(((LispPair *) rest)->head, Qnil); end = args; } else { LispVal *new_end = Fpair(((LispPair *) rest)->head, Qnil); Fsettail(end, new_end); refcount_unref(new_end); end = new_end; } rest = ((LispPair *) rest)->tail; } if (LISTP(HEAD(rest))) { // ensure the list is not circular refcount_ref(args); WITH_CLEANUP(args, { list_length(Fhead(rest)); // }); if (NILP(args)) { args = HEAD(rest); } else { Fsettail(end, HEAD(rest)); } } else { if (NILP(args)) { args = Fpair(((LispPair *) rest)->head, Qnil); end = args; } else { LispVal *new_end = Fpair(((LispPair *) rest)->head, Qnil); Fsettail(end, new_end); refcount_unref(new_end); end = new_end; } } LispVal *retval; WITH_CLEANUP_DOUBLE_PTR(args, { retval = Ffuncall(function, args); // }); return retval; } static LispVal *lookup_lexical_macro(LispVal *name, LispVal *lexical_macros) { if (!SYMBOLP(name)) { return Qunbound; } LispVal *res = Fplist_get(lexical_macros, name, Qunbound, Qnil); if (FUNCTIONP(res)) { return res; } refcount_unref(res); return Qunbound; } static inline LispVal *expand_function_as_macro(LispFunction *fobj, LispVal *args) { return Ffuncall((LispVal *) fobj, args); } DEFUN(macroexpand_1, "macroexpand-1", (LispVal * form, LispVal *lexical_macros), "(form &opt lexical-macros)", "Expand the toplevel macro one time in FORM. LEXICAL-MACROS is a plist " "of symbols and functions that are considered to be addition macros to " "expand. LEXICAL-MACROS take priority over global macros.") { if (PAIRP(form)) { LispVal *lex_res = lookup_lexical_macro(HEAD(form), lexical_macros); LispFunction *fobj = (LispFunction *) Qunbound; if (lex_res != Qunbound) { return expand_function_as_macro((LispFunction *) lex_res, TAIL(form)); } else if (FUNCTIONP(HEAD(form))) { fobj = refcount_ref(HEAD(form)); } else if (PAIRP(HEAD(form)) && HEAD(HEAD(form)) == Qlambda) { fobj = (LispFunction *) Feval(HEAD(form), the_stack->lexenv); assert(FUNCTIONP(fobj)); } else { fobj = (LispFunction *) Fsymbol_function(HEAD(form), Qt); } if (!FUNCTIONP(fobj) || fobj->is_builtin || !fobj->is_macro) { refcount_unref(fobj); return refcount_ref(form); } LispVal *expansion = Qnil; LispVal *return_ptr = Fpair(Qnil, Qnil); WITH_CLEANUP(return_ptr, { WITH_CLEANUP(fobj, { WITH_PUSH_FRAME_NO_REF_HANDLING_THROWS( refcount_ref(fobj), refcount_ref(TAIL(form)), false, { the_stack->hidden = false; if (!NILP(fobj->return_tag)) { the_stack->return_tag = refcount_ref(fobj->return_tag); setup_return_handler(fobj->return_tag, return_ptr); } the_stack->lexenv = refcount_ref(fobj->lexenv); process_lisp_args(Fhead(form), fobj, Ftail(form), &the_stack->lexenv); expansion = Fprogn(fobj->body); }, { expansion = refcount_ref(HEAD(return_ptr)); // }); }); }); return expansion; } else { return refcount_ref(form); } } DEFUN(macroexpand_toplevel, "macroexpand-toplevel", (LispVal * form, LispVal *lexical_macros), "(form &opt lexical-macros)", "Expand the toplevel of FORM until it is no longer a macro. " "LEXICAL-MACROS is the same as for macroexpand-1.") { if (PAIRP(form)) { LispVal *out = refcount_ref(form); void *cl_handler = register_cleanup(&unref_double_ptr, &out); while (PAIRP(out) && !NILP(Fmacrop(HEAD(out), lexical_macros))) { LispVal *new_out = Fmacroexpand_1(out, lexical_macros); refcount_unref(out); out = new_out; } cancel_cleanup(cl_handler); return out; } else { return refcount_ref(form); } } static LispVal *filter_body_form(LispVal *form, LispVal *(*func)(LispVal *body, void *user_data), void *user_data); #define EXPAND_HEAD(form) \ { \ LispVal *expansion = filter_body_form(HEAD(form), func, user_data); \ WITH_CLEANUP(expansion, { Fsethead(form, expansion); }); \ } static void expand_lambda_list(LispVal *list, LispVal *(*func)(LispVal *body, void *user_data), void *user_data) { bool enable_extended = false; FOREACH_TAIL(entry, list) { if (enable_extended && PAIRP(HEAD(entry))) { LispVal *copy = Fcopy_list(HEAD(entry)); Fsethead(entry, copy); refcount_unref(copy); if (PAIRP(TAIL(copy))) { EXPAND_HEAD(TAIL(copy)); } } else if (HEAD(entry) == Qrest) { enable_extended = false; } else if (HEAD(entry) == Qopt || HEAD(entry) == Qkey) { enable_extended = true; } } } STATIC_DEFMACRO(internal_real_return, "internal-real-return", (LispVal * name, LispVal *tag, LispVal *value), "(name tag value)", " Internal function.") { for (StackFrame *cur = the_stack; cur; cur = cur->next) { if (!NILP(cur->return_tag) && cur->enable_handlers && cur->return_tag == tag) { Fthrow(cur->return_tag, const_list(false, 1, Feval(value, the_stack->lexenv))); } } Fthrow(Qreturn_frame_error, const_list(false, 2, refcount_ref(name), Feval(value, the_stack->lexenv))); } static void expand_builtin_macro(LispFunction *fobj, LispVal *args, LispVal *(*func)(LispVal *body, void *user_data), void *user_data) { if (fobj->builtin == (lisp_function_ptr_t) Fquote) { return; // do nothing } else if (fobj->builtin == (lisp_function_ptr_t) Fsetq) { bool is_var = true; FOREACH_TAIL(form, args) { if (!is_var) { EXPAND_HEAD(form); } is_var = !is_var; } } else if (fobj->builtin == (lisp_function_ptr_t) Freturn_from) { if (PAIRP(args) && PAIRP(TAIL(args))) { EXPAND_HEAD(TAIL(args)); } } else if (fobj->builtin == (lisp_function_ptr_t) Finternal_real_return) { if (PAIRP(args) && PAIRP(TAIL(args)) && PAIRP(TAIL(TAIL(args)))) { EXPAND_HEAD(TAIL(TAIL(args))); } } else if (fobj->builtin == (lisp_function_ptr_t) Fcondition_case) { if (PAIRP(args)) { EXPAND_HEAD(args); FOREACH_TAIL(entry_tail, TAIL(args)) { LispVal *copy = Fcopy_list(HEAD(entry_tail)); Fsethead(entry_tail, copy); refcount_unref(copy); if (PAIRP(HEAD(entry_tail))) { FOREACH_TAIL(form, TAIL(HEAD(entry_tail))) { EXPAND_HEAD(form); } } } } } else if (fobj->builtin == (lisp_function_ptr_t) Flambda) { if (!LISTP(args)) { return; } LispVal *expand_from = TAIL(args); // skip lambda list if (!LISTP(expand_from)) { return; } LispVal *copy = Fcopy_list(HEAD(args)); Fsethead(args, copy); refcount_unref(copy); expand_lambda_list(HEAD(args), func, user_data); LispVal *first_form = HEAD(expand_from); if (PAIRP(first_form) && HEAD(first_form) == Qdeclare) { expand_from = TAIL(expand_from); // declare statement if (!LISTP(expand_from)) { return; } } FOREACH_TAIL(form, expand_from) { EXPAND_HEAD(form); } } else { FOREACH_TAIL(form, args) { EXPAND_HEAD(form); } } } #undef EXPAND_HEAD // func should ref its return value static LispVal *filter_body_form(LispVal *form, LispVal *(*func)(LispVal *body, void *user_data), void *user_data) { LispVal *toplevel_orig = func(form, user_data); if (PAIRP(toplevel_orig)) { LispVal *toplevel; WITH_CLEANUP(toplevel_orig, { toplevel = Fcopy_list(toplevel_orig); // }); WITH_PUSH_FRAME(Qnil, Qnil, true, { void *cl_handler = register_cleanup(&unref_double_ptr, &toplevel); if (PAIRP(toplevel)) { LispFunction *fobj = NULL; if (FUNCTIONP(HEAD(toplevel))) { fobj = refcount_ref(HEAD(toplevel)); } else if (SYMBOLP(HEAD(toplevel))) { fobj = (LispFunction *) Fsymbol_function(HEAD(toplevel), Qt); } if (fobj && FUNCTIONP(fobj)) { WITH_CLEANUP(fobj, { if (fobj->is_builtin && fobj->is_macro) { expand_builtin_macro(fobj, TAIL(toplevel), func, user_data); } else { FOREACH_TAIL(tail, TAIL(toplevel)) { Fsethead(tail, filter_body_form(HEAD(tail), func, user_data)); } } }); } } cancel_cleanup(cl_handler); }); return toplevel; } else { return toplevel_orig; } return Qnil; } static LispVal *filter_body_tree(LispVal *body, LispVal *(*func)(LispVal *body, void *user_data), void *user_data) { LispVal *start = Qnil; LispVal *end = Qnil; FOREACH(form, body) { LispVal *filtered = filter_body_form(form, func, user_data); if (NILP(start)) { start = Fpair(filtered, Qnil); end = start; } else { LispVal *new_end = Fpair(filtered, Qnil); Fsettail(end, new_end); refcount_unref(new_end); end = new_end; } refcount_unref(filtered); } return start; } static LispVal *macroexpand_toplevel_as_callback(LispVal *form, void *lexical_macros) { return Fmacroexpand_toplevel(form, lexical_macros); } DEFUN(macroexpand_all, "macroexpand-all", (LispVal * form, LispVal *lexical_macros), "(form &opt lexical-macros)", "Expand all macros in the toplevel and arguments of FORM. LEXICAL-MACROS " "is as it is for macroexpand-1.") { return filter_body_form(form, macroexpand_toplevel_as_callback, lexical_macros); } // ################# // # Special Forms # // ################# DEFMACRO(quote, "'", (LispVal * form), "(form)", "Return FORM.") { return refcount_ref(form); } DEFMACRO(if, "if", (LispVal * cond, LispVal *t, LispVal *nil), "(cond then &rest else)", "If COND evaluates to a non-nil value, evaluate THEN, otherwise, " "evaluate each form in ELSE.") { LispVal *res = Feval(cond, the_stack->lexenv); LispVal *retval = Qnil; WITH_PUSH_FRAME(Qnil, Qnil, true, { if (!NILP(res)) { retval = Feval(t, the_stack->lexenv); } else { retval = Fprogn(nil); } }); return retval; } static void set_symbol_in_lexenv(LispVal *key, LispVal *newval, LispVal *lexenv) { if (VALUE_CONSTANTP(key)) { Fthrow(Qconstant_value_error, Fpair(key, Qnil)); } LispVal *val_pair = Qnil; if (SPECIALP(key)) { val_pair = Fplist_assoc(the_stack->dynenv, key, Qnil); } else { val_pair = Fplist_assoc(lexenv, key, Qnil); } if (PAIRP(val_pair)) { Fsethead(TAIL(val_pair), newval); } else { refcount_unref(((LispSymbol *) key)->value); ((LispSymbol *) key)->value = refcount_ref(newval); } } DEFMACRO( setq, "setq", (LispVal * args), "(&rest args)", "ARGS is a plist of symbols and values. Set each of the symbols to their " "respective value. Return the last value. Each symbol is set lexically if " "a lexical binding exists. Otherwise the symbol's value is altered.") { size_t len = list_length(args); if (!len || len % 2) { Fthrow(Qargument_error, Fpair(Qsetq, Qnil)); } LispVal *retval = Qnil; FOREACH_TAIL(tail, args) { CHECK_TYPE(TYPE_SYMBOL, HEAD(tail)); LispVal *name = HEAD(tail); tail = TAIL(tail); retval = Feval(HEAD(tail), the_stack->lexenv); WITH_CLEANUP(retval, { set_symbol_in_lexenv(name, retval, the_stack->lexenv); // }); } return retval; } DEFMACRO(progn, "progn", (LispVal * forms), "(&rest forms)", "Evaluate each of FORMS.") { LispVal *retval = Qnil; FOREACH(form, forms) { refcount_unref(retval); retval = Feval(form, the_stack->lexenv); } return retval; } DEFMACRO( condition_case, "condition-case", (LispVal * form, LispVal *rest), "(form &rest handlers)", "Evaluate FORM. If an exception is thrown, evaluate the corresponding " "handler.\n" "Each handler is HANDLERS is a list with the head being a list of a " "variable followed by a symbol or a list of symbols and the tail " "being any number of forms. Each symbol is an error to catch. During " "the executing of the following forms, the variable will be bound to " "information about the exception.\n" "Optionally, the symbols :success or :finally can be used in place of an " "error or list of errors. :finally forms will be weather or not an error " "is caught after FORM is done being evaluated. :success forms will be run " "if the evaluation of FORM finished with no errors.") { bool success = false; LispVal *success_form = Qunbound; LispVal *finally_form = Qunbound; LispVal *retval = Qnil; WITH_PUSH_FRAME_NO_REF_HANDLING_THROWS( Qnil, Qnil, true, { void *cl_handler = register_cleanup(&unref_double_ptr, &success_form); void *cl_handler2 = register_cleanup(&unref_double_ptr, &finally_form); FOREACH(entry, rest) { if (HEAD(entry) == Qkw_success) { if (success_form != Qunbound) { Fthrow(Qmalformed_lambda_list_error, Qnil); } success_form = Fpair(Qprogn, TAIL(entry)); } else if (HEAD(entry) == Qkw_finally) { if (finally_form != Qunbound) { Fthrow(Qmalformed_lambda_list_error, Qnil); } finally_form = Fpair(Qprogn, TAIL(entry)); } else { LispVal *var = HEAD(HEAD(entry)); LispVal *types = HEAD(TAIL(HEAD(entry))); if (!PAIRP(types)) { types = const_list(true, 1, types); } else { types = refcount_ref(types); } WITH_CLEANUP(types, { IGNORE(); // unconfuse clang-format FOREACH(type, types) { LispVal *handler = push_many(TAIL(entry), 2, Qprogn, var); puthash(the_stack->handlers, type, handler); refcount_unref(handler); } }); } } cancel_cleanup(cl_handler2); if (finally_form != Qunbound) { the_stack->unwind_form = finally_form; } retval = Feval(form, the_stack->lexenv); cancel_cleanup(cl_handler); success = true; }, { retval = refcount_ref(stack_return); }); // call this out here so it is not covered by the handlers if (success && success_form != Qunbound) { void *cl_handler = register_cleanup(&refcount_unref_as_callback, retval); WITH_CLEANUP(success_form, { refcount_unref(Feval(success_form, the_stack->lexenv)); // }); cancel_cleanup(cl_handler); } return retval; } // true if the form was a declare form static bool parse_function_declare(LispVal *form, LispVal **name_ptr, bool *is_macro_ptr, LispVal *props_ht) { if (PAIRP(form) && HEAD(form) == Qdeclare) { FOREACH(elt, TAIL(form)) { if (name_ptr && PAIRP(elt) && HEAD(elt) == Qname && PAIRP(TAIL(elt))) { *name_ptr = HEAD(TAIL(elt)); } else if (is_macro_ptr && (elt == Qmacro || (PAIRP(elt) && HEAD(elt) == Qmacro))) { *is_macro_ptr = true; } else if (SYMBOLP(elt)) { puthash(props_ht, elt, Qt); } else if (PAIRP(elt) && SYMBOLP(HEAD(elt)) && LISTP(TAIL(elt))) { puthash(props_ht, HEAD(elt), HEAD(TAIL(elt))); } } return true; } return false; } struct NameAndReturnTag { LispVal *name; LispVal *return_tag; }; static LispVal *expand_function_body_callback(LispVal *body, void *data) { struct NameAndReturnTag *name_and_return_tag = data; LispVal *expansion = Fmacroexpand_toplevel(body, Qnil); // this mess checks that the call is exactly one of // - (return-from 'symbol) // - (return-from 'symbol val) if (PAIRP(expansion) && HEAD(expansion) == Qreturn_from && PAIRP(TAIL(expansion)) && LISTP(TAIL(TAIL(expansion))) && NILP(TAIL(TAIL(TAIL(expansion)))) && SYMBOLP(HEAD(TAIL(expansion))) && HEAD(TAIL(expansion)) == name_and_return_tag->name) { LispVal *retval = Qnil; if (!NILP(TAIL(TAIL(expansion)))) { retval = refcount_ref(HEAD(TAIL(TAIL(expansion)))); } refcount_unref(expansion); return const_list(false, 4, Qinternal_real_return, refcount_ref(name_and_return_tag->name), refcount_ref(name_and_return_tag->return_tag), retval); } else if (PAIRP(expansion) && HEAD(expansion) == Qinternal_real_return && list_length(expansion) == 4 && HEAD(TAIL(expansion)) == name_and_return_tag->name && HEAD(TAIL(TAIL(expansion))) != name_and_return_tag->return_tag) { Fsethead(TAIL(TAIL(expansion)), name_and_return_tag->return_tag); } return expansion; } static inline LispVal *expand_function_body(LispVal *name, LispVal *return_tag, LispVal *body) { return filter_body_tree( body, expand_function_body_callback, &(struct NameAndReturnTag) {.name = name, .return_tag = return_tag}); } static LispVal *macroexpand_all_as_callback(LispVal *form, void *ignored) { return Fmacroexpand_all(form, Qnil); } static inline void expand_lambda_list_for_toplevel(LispVal *list) { expand_lambda_list(list, macroexpand_all_as_callback, NULL); } DEFMACRO(lambda, "lambda", (LispVal * args, LispVal *body), "(args &rest body)", "Return a new function.") { LispVal *doc = Qnil; if (STRINGP(HEAD(body))) { doc = HEAD(body); body = TAIL(body); } LispVal *name = Qunbound; bool is_macro = false; LispVal *props_ht = make_lisp_hashtable(Qnil, Qnil); if (parse_function_declare(HEAD(body), &name, &is_macro, props_ht)) { body = TAIL(body); } LispVal *return_tag; LispVal *tag_name; if (name == Qunbound) { name = Qlambda; tag_name = Qnil; return_tag = make_lisp_symbol(LISPVAL(((LispSymbol *) Qnil)->name)); } else { if (!SYMBOLP(name)) { refcount_unref(props_ht); CHECK_TYPE(TYPE_SYMBOL, name); } return_tag = make_lisp_symbol(LISPVAL(((LispSymbol *) name)->name)); tag_name = name; } LispVal *func = Qnil; WITH_CLEANUP(props_ht, { WITH_CLEANUP(return_tag, { LispVal *expanded_body = expand_function_body(tag_name, return_tag, body); LispVal *exp_args = Fcopy_list(args); WITH_CLEANUP(exp_args, { expand_lambda_list_for_toplevel(exp_args); WITH_CLEANUP(expanded_body, { func = make_lisp_function(name, return_tag, args, the_stack->lexenv, expanded_body, doc, is_macro, props_ht); }); }); }); }); return func; } DEFMACRO(while, "while", (LispVal * cond, LispVal *body), "(cond &rest body)", "Evaluate COND, if its result is non-nil evaluate BODY. Repeat this " "until COND returns nil. Then return nil.") { LispVal *evaled_cond; while (!NILP(evaled_cond = Feval(cond, the_stack->lexenv))) { refcount_unref(evaled_cond); refcount_unref(Fprogn(body)); } return Qnil; } DEFMACRO(and, "and", (LispVal * rest), "(&rest rest)", "Evaluate the first argument in REST. If its result is non-nil " "evaluate the next argument. Otherwise, return nil. Repeat this until " "one argument returns nil or there are no arguments left. If no " "argument returned nil, return the result of the last argument.") { LispVal *retval = Qnil; FOREACH(cond, rest) { LispVal *nc; WITH_CLEANUP(retval, { nc = Feval(cond, the_stack->lexenv); // }); if (NILP(nc)) { return Qnil; } retval = nc; } return retval; } DEFMACRO(or, "or", (LispVal * rest), "(&rest rest)", "Evaluate the first argument in REST. If it returns non-nil, return " "its value. Oterwise, evaluate the next argument. Repeat this until " "there are no more arguments at which time nil is returned.") { FOREACH(cond, rest) { LispVal *nc = Feval(cond, the_stack->lexenv); if (!NILP(nc)) { return nc; } } return Qnil; } DEFMACRO(in_package, "in-package", (LispVal * package), "(package)", "Set the current package to PACKAGE.") { return Fset_current_package(package); } DEFMACRO(return_from, "return-from", (LispVal * name, LispVal *value), "(name &opt value)", "Return VALUE from the function named NAME.") { Fthrow(Qreturn_frame_error, const_list(false, 2, refcount_ref(name), Feval(value, the_stack->lexenv))); } // ###################### // # Function Functions # // ###################### DEFUN(functionp, "functionp", (LispVal * val), "(obj)", "Return non-nil if OBJ is a non-macro function object or a symbol whose " "value as a function resolves to a non-macro function object.") { if (FUNCTIONP(val) && !((LispFunction *) val)->is_macro) { return Qt; } else if (SYMBOLP(val)) { LispVal *res = Fsymbol_function(val, Qt); LispVal *retval = LISP_BOOL(FUNCTIONP(res) && !((LispFunction *) res)->is_macro); refcount_unref(res); return retval; } return Qnil; } DEFUN(macrop, "macrop", (LispVal * val, LispVal *lexical_macros), "(obj &opt lexical-macros)", "Return non-nil if OBJ is a macro object, a symbol whose value as a " "function resolves to a macro object, or a symbol with a definition in " "LEXICAL-MACROS.") { if (FUNCTIONP(val) && !((LispFunction *) val)->is_builtin && ((LispFunction *) val)->is_macro) { return Qt; } else if (SYMBOLP(val)) { if (lookup_lexical_macro(val, lexical_macros) != Qunbound) { return Qt; } LispVal *res = Fsymbol_function(val, Qt); LispVal *retval = LISP_BOOL(FUNCTIONP(res) && !((LispFunction *) res)->is_builtin && ((LispFunction *) res)->is_macro); refcount_unref(res); return retval; } return Qnil; } DEFUN(builtinp, "builtinp", (LispVal * val), "(obj)", "Return non-nil if OBJ is a built-in function or a symbol whose value as " "a function resolves to a built-in function.") { if (FUNCTIONP(val) && ((LispFunction *) val)->is_builtin && !((LispFunction *) val)->is_macro) { return Qt; } else if (SYMBOLP(val)) { LispVal *res = Fsymbol_function(val, Qt); LispVal *retval = LISP_BOOL(FUNCTIONP(res) && ((LispFunction *) res)->is_builtin && !((LispFunction *) res)->is_macro); refcount_unref(res); return retval; } return Qnil; } DEFUN(special_form_p, "special-form-p", (LispVal * val), "(obj)", "Return non-nil if OBJ is a special-function (built-in macro) or a " "symbol whose value as a function resolves to such a function.") { if (FUNCTIONP(val) && ((LispFunction *) val)->is_builtin && ((LispFunction *) val)->is_macro) { return Qt; } else if (SYMBOLP(val)) { LispVal *res = Fsymbol_function(val, Qt); LispVal *retval = LISP_BOOL(FUNCTIONP(res) && ((LispFunction *) res)->is_builtin && ((LispFunction *) res)->is_macro); refcount_unref(res); return retval; } return Qnil; } DEFUN(function_docstr, "function-docstr", (LispVal * func), "(func)", "Return the documentation string for FUNC, or nil if it has no " "documentation string.") { if (FUNCTIONP(func)) { return ((LispFunction *) func)->doc; } LispFunction *fobj = (LispFunction *) Fsymbol_function(func, Qt); if (!FUNCTIONP(fobj)) { refcount_unref(fobj); CHECK_TYPE(TYPE_FUNCTION, fobj); } LispVal *retval = refcount_ref(fobj->doc); refcount_unref(fobj); return retval; } DEFUN(function_properties, "function-properties", (LispVal * func), "(func)", "Return the property hash table of FUNC.") { CHECK_TYPE(TYPE_FUNCTION, func); return refcount_ref(((LispFunction *) func)->props); } void free_opt_arg_desc(void *obj) { struct OptArgDesc *oad = obj; refcount_unref(oad->name); refcount_unref(oad->default_form); refcount_unref(oad->pred_var); lisp_free(oad); } void set_function_args(LispFunction *func, LispVal *args) { refcount_unref(func->args); refcount_unref(func->kwargs); refcount_unref(func->rargs); refcount_unref(func->oargs); refcount_unref(func->rest_arg); LispVal *found_args = make_lisp_hashtable(Qnil, Qnil); enum { REQ, OPT, KEY, REST, MUST_CHANGE, } mode = REQ; bool has_opt = false; bool has_key = false; bool has_rest = false; func->n_req = 0; func->rargs = Qnil; func->n_opt = 0; func->oargs = Qnil; func->rest_arg = Qnil; func->kwargs = make_lisp_hashtable(Qnil, Qnil); func->allow_other_keys = false; LispVal *rargs_end = Qnil; LispVal *oargs_end = Qnil; FOREACH(arg, args) { if (arg == Qopt) { if (has_opt || mode == REST) { goto malformed; } has_opt = true; mode = OPT; } else if (arg == Qkey) { if (has_key || mode == REST) { goto malformed; } has_key = true; mode = KEY; } else if (arg == Qrest) { if (has_rest) { goto malformed; } has_rest = true; mode = REST; } else if (arg == Qallow_other_keys) { if (func->allow_other_keys || mode != KEY) { goto malformed; } func->allow_other_keys = true; mode = MUST_CHANGE; } else { switch (mode) { case REQ: if (!SYMBOLP(arg) || VALUE_CONSTANTP(arg) || !NILP(gethash(found_args, arg, Qnil))) { goto malformed; } if (NILP(func->rargs)) { func->rargs = Fpair(arg, Qnil); rargs_end = func->rargs; } else { LispVal *new_end = Fpair(arg, Qnil); Fsettail(rargs_end, new_end); refcount_unref(new_end); rargs_end = new_end; } puthash(found_args, arg, Qt); ++func->n_req; break; case OPT: { LispVal *desc = ALLOC_USERPTR(struct OptArgDesc, free_opt_arg_desc); USERPTR(struct OptArgDesc, desc)->index = 0; if (!parse_opt_arg_entry(arg, USERPTR(struct OptArgDesc, desc), found_args)) { refcount_unref(desc); goto malformed; } if (NILP(func->oargs)) { func->oargs = Fpair(desc, Qnil); oargs_end = func->oargs; } else { LispVal *new_end = Fpair(desc, Qnil); Fsettail(oargs_end, new_end); refcount_unref(new_end); oargs_end = new_end; } refcount_unref(desc); puthash(found_args, USERPTR(struct OptArgDesc, desc)->name, Qt); if (!NILP(USERPTR(struct OptArgDesc, desc)->pred_var)) { puthash(found_args, USERPTR(struct OptArgDesc, desc)->pred_var, Qt); } ++func->n_opt; } break; case KEY: { LispVal *desc = ALLOC_USERPTR(struct OptArgDesc, free_opt_arg_desc); if (!parse_opt_arg_entry(arg, USERPTR(struct OptArgDesc, desc), found_args)) { refcount_unref(desc); goto malformed; } USERPTR(struct OptArgDesc, desc)->index = ((LispHashtable *) func->kwargs)->count; LispString *sn = ((LispSymbol *) USERPTR(struct OptArgDesc, desc)->name) ->name; char kns[sn->length + 2]; kns[0] = ':'; memcpy(kns + 1, sn->data, sn->length); kns[sn->length + 1] = '\0'; LispVal *kn = make_lisp_string(kns, sn->length + 1, false, false); LispVal *keyword = Fintern(kn, Qnil, Qnil); puthash(func->kwargs, keyword, desc); refcount_unref(keyword); refcount_unref(kn); refcount_unref(desc); puthash(found_args, USERPTR(struct OptArgDesc, desc)->name, Qt); if (!NILP(USERPTR(struct OptArgDesc, desc)->pred_var)) { puthash(found_args, USERPTR(struct OptArgDesc, desc)->pred_var, Qt); } } break; case REST: if (!NILP(func->rest_arg)) { goto malformed; } else if (!SYMBOLP(arg) || VALUE_CONSTANTP(arg)) { goto malformed; } else if (!NILP(Fgethash(found_args, arg, Qnil))) { goto malformed; } func->rest_arg = refcount_ref(arg); mode = MUST_CHANGE; break; case MUST_CHANGE: goto malformed; } } } refcount_unref(found_args); // do this last func->args = refcount_ref(args); return; malformed: refcount_unref(func->rargs); refcount_unref(func->oargs); refcount_unref(func->rest_arg); refcount_unref(func->kwargs); refcount_unref(found_args); Fthrow(Qmalformed_lambda_list_error, Fpair(args, Qnil)); } // ########################### // # Pair and List Functions # // ########################### DEFUN(pairp, "pairp", (LispVal * val), "(obj)", "Return non-nil if OBJ is a pair.") { return LISP_BOOL(PAIRP(val)); } DEFUN(atom, "atom", (LispVal * val), "(obj)", "Return non-nil if OBJ is not a pair. Nil is not a pair.") { return LISP_BOOL(ATOM(val)); } DEFUN(pair, "pair", (LispVal * head, LispVal *tail), "(head tail)", "Construct a new pair from HEAD and TAIL.") { return make_lisp_pair(head, tail); } DEFUN(head, "head", (LispVal * list), "(list)", "Return the first element in LIST.") { return refcount_ref(HEAD(list)); } DEFUN(tail, "tail", (LispVal * list), "(list)", "Return everything but the first element in LIST.") { return refcount_ref(TAIL(list)); } DEFUN(sethead, "sethead", (LispVal * pair, LispVal *head), "(pair head)", "Set the head of PAIR to HEAD.") { CHECK_TYPE(TYPE_PAIR, pair); refcount_unref(((LispPair *) pair)->head); ((LispPair *) pair)->head = refcount_ref(head); return Qnil; } DEFUN(settail, "settail", (LispVal * pair, LispVal *tail), "(pair tail)", "Set the tail of PAIR to TAIL.") { CHECK_TYPE(TYPE_PAIR, pair); refcount_unref(((LispPair *) pair)->tail); ((LispPair *) pair)->tail = refcount_ref(tail); return Qnil; } // lists DEFUN(listp, "listp", (LispVal * val), "(obj)", "Return non-nil if OBJ is a pair or nil.") { return LISP_BOOL(LISTP(val)); } DEFUN(list_length, "list-length", (LispVal * list), "(list)", "Return the length of LIST. Throw an error if LIST is circular.") { return make_lisp_integer(list_length(list)); } DEFUN(copy_list, "copy-list", (LispVal * list), "(list)", "Return a shallow copy of LIST.") { if (NILP(list)) { return Qnil; } CHECK_TYPE(TYPE_PAIR, list); LispVal *copy = Qnil; LispVal *copy_end; WITH_PUSH_FRAME(Qnil, Qnil, true, { void *cl_cleanup = register_cleanup(&unref_double_ptr, ©); FOREACH(elt, list) { if (NILP(copy)) { copy = Fpair(elt, Qnil); copy_end = copy; } else { LispVal *new_end = Fpair(elt, Qnil); Fsettail(copy_end, new_end); refcount_unref(new_end); copy_end = new_end; } } cancel_cleanup(cl_cleanup); }); return copy; } DEFUN(copy_tree, "copy-tree", (LispVal * tree), "(tree)", "Return a copy of TREE and each sub-tree of TREE.") { if (NILP(tree)) { return Qnil; } CHECK_TYPE(TYPE_PAIR, tree); LispPair *tortise = (LispPair *) tree; LispPair *hare = (LispPair *) tortise->tail; LispVal *copy = Qnil; LispVal *copy_end; WITH_PUSH_FRAME(Qnil, Qnil, true, { void *cl_handle = register_cleanup(&unref_double_ptr, ©); while (!NILP(tortise)) { if (!LISTP(LISPVAL(tortise))) { break; } else if (tortise == hare) { refcount_unref(copy); Fthrow(Qcircular_error, Qnil); } LispVal *elt = tortise->head; if (PAIRP(elt)) { elt = Fcopy_tree(elt); } else { refcount_ref(elt); } if (NILP(copy)) { copy = Fpair(elt, Qnil); copy_end = copy; } else { LispVal *new_end = Fpair(elt, Qnil); Fsettail(copy_end, new_end); refcount_unref(new_end); copy_end = new_end; } refcount_unref(elt); tortise = (LispPair *) tortise->tail; if (PAIRP(hare)) { if (PAIRP(((LispPair *) hare)->tail)) { hare = (LispPair *) ((LispPair *) hare->tail)->tail; } else if (NILP(((LispPair *) hare)->tail)) { hare = (LispPair *) Qnil; } } } cancel_cleanup(cl_handle); }); return copy; } size_t list_length(LispVal *obj) { if (NILP(obj)) { return 0; } CHECK_TYPE(TYPE_PAIR, obj); size_t length = 0; LispPair *tortise = (LispPair *) obj; LispPair *hare = (LispPair *) tortise->tail; while (!NILP(tortise)) { if (!LISTP(LISPVAL(tortise))) { break; } else if (tortise == hare) { Fthrow(Qcircular_error, Qnil); } ++length; tortise = (LispPair *) tortise->tail; if (PAIRP(hare)) { if (PAIRP(((LispPair *) hare)->tail)) { hare = (LispPair *) ((LispPair *) hare->tail)->tail; } else if (NILP(((LispPair *) hare)->tail)) { hare = (LispPair *) Qnil; } } } return length; } // plists static bool call_eq_pred(LispVal *pred, LispVal *v1, LispVal *v2) { if (NILP(pred)) { return !NILP(Feq(v1, v2)); } else { LispVal *fcall_args = const_list(true, 2, v1, v2); bool res = false; WITH_CLEANUP(fcall_args, { LispVal *lvpr = Ffuncall(pred, fcall_args); // res = !NILP(lvpr); refcount_unref(lvpr); }); return res; } } DEFUN(plist_get, "plist-get", (LispVal * plist, LispVal *key, LispVal *def, LispVal *pred), "(plist key &opt def pred)", "Find and return the value associated with KEY in PLIST. If it is not " "found, return DEF. Keys are compared with PRED.") { for (LispVal *cur = plist; !NILP(cur); cur = TAIL(TAIL(cur))) { if (call_eq_pred(pred, key, HEAD(cur))) { if (NILP(TAIL(cur))) { return refcount_ref(def); } return refcount_ref(HEAD(TAIL(cur))); } } return refcount_ref(def); } DEFUN(plist_set, "plist-set", (LispVal * plist, LispVal *key, LispVal *value, LispVal *pred), "(plist key value &opt pred)", "Set the value associated with KEY in PLIST to VALUE. Keys are compared " "with PRED. Return the modified PLIST.") { for (LispVal *cur = plist; !NILP(cur); cur = TAIL(TAIL(cur))) { if (call_eq_pred(pred, key, HEAD(cur))) { if (NILP(TAIL(cur))) { break; } Fsethead(TAIL(cur), value); return refcount_ref(plist); } } return push_many(plist, 2, value, key); } DEFUN(plist_rem, "plist-rem", (LispVal * plist, LispVal *key, LispVal *pred), "(plist key &rest pred)", "Removed KEY and its associated value from PLIST. Keys are compared with " "PRED. Return the modified PLIST.") { for (LispVal *prev = Qnil, *cur = plist; !NILP(cur); prev = cur, cur = TAIL(TAIL(cur))) { if (call_eq_pred(pred, key, HEAD(cur))) { if (NILP(prev)) { return refcount_ref(TAIL(TAIL(plist))); } else { Fsettail(TAIL(prev), TAIL(TAIL(cur))); return refcount_ref(plist); } } } return refcount_ref(plist); } DEFUN(plist_assoc, "plist-assoc", (LispVal * plist, LispVal *key, LispVal *pred), "(plist key &rest pred)", "Return the sub-list starting with KEY from PLIST. Keys are compared " "with PRED.") { for (LispVal *cur = plist; !NILP(cur); cur = TAIL(TAIL(cur))) { if (call_eq_pred(pred, key, HEAD(cur))) { return cur; } } return Qnil; } // ##################### // # Package Functions # // ##################### static LispVal *normalize_package(LispVal *arg) { if (STRINGP(arg) || SYMBOLP(arg)) { LispVal *found = Ffind_package(arg); if (!PACKAGEP(found)) { refcount_unref(found); Fthrow(Qunknown_package_error, const_list(true, 1, arg)); } return found; } else if (PACKAGEP(arg)) { return refcount_ref(arg); } else { Fthrow(Qtype_error, Qnil); } } DEFUN(packagep, "packagep", (LispVal * val), "(obj)", "Return non-nil if OBJ is a package object.") { return LISP_BOOL(PACKAGEP(val)); } DEFUN(make_package, "make-package", (LispVal * name), "(name)", "Return a new un-registed package object called NAME.") { if (SYMBOLP(name)) { name = Fsymbol_name(name); } else { name = refcount_ref(name); } LispVal *np = make_lisp_package(name); refcount_unref(name); return np; } DEFUN(package_name, "package-name", (LispVal * package), "(package)", "Return the name of PACKAGE.") { CHECK_TYPE(TYPE_PACKAGE, package); return LISPVAL(((LispPackage *) package)->name); } DEFUN(register_package, "register-package", (LispVal * package), "(package)", "Register PACKAGE with the reader.") { if (STRINGP(package)) { package = make_lisp_package(package); } else if (SYMBOLP(package)) { package = make_lisp_package(LISPVAL(((LispSymbol *) package)->name)); } else { CHECK_TYPE(TYPE_PACKAGE, package); package = refcount_ref(package); } LispVal *found = Ffind_package(package); if (!NILP(found)) { refcount_unref(package); Fthrow(Qpackage_exists_error, const_list(true, 1, package)); } puthash(package_table, LISPVAL(((LispPackage *) package)->name), package); return package; } DEFUN(current_package, "current-package", (void), "()", "Return the current package.") { return refcount_ref(current_package); } DEFUN(set_current_package, "set-current-package", (LispVal * package), "(package)", "Set the current package.") { LispVal *new = normalize_package(package); LispVal *old = current_package; current_package = new; refcount_unref(old); return refcount_ref(current_package); } DEFUN(mapsymbols, "mapsymbols", (LispVal * func, LispVal *package), "(func &opt package)", "Call FUNC for each symbol in PACKAGE, defaulting to the current " "package.") { LispPackage *pkg; if (NILP(package)) { pkg = refcount_ref(current_package); } else { pkg = (LispPackage *) normalize_package(package); } WITH_CLEANUP(pkg, { IGNORE(); HT_FOREACH_VALID_INDEX(pkg->obarray, i) { LispVal *args = const_list(true, 1, HASH_VALUE(pkg->obarray, i)); refcount_unref(Ffuncall(func, args)); } }); return Qnil; } DEFUN(export_symbol, "export-symbol", (LispVal * symbol), "(symbol)", "Mark SYMBOL as exported from its package. If SYMBOL is not interned in " "any package, throw and error.") { if (SYMBOLP(symbol)) { LispSymbol *sym = (LispSymbol *) symbol; LispPackage *pkg = (LispPackage *) sym->package; if (NILP(pkg)) { Fthrow(Qtype_error, Qnil); } puthash(pkg->exported_sym_table, symbol, Qt); } else if (LISTP(symbol)) { FOREACH(cur, symbol) { CHECK_TYPE(TYPE_SYMBOL, cur); Fexport_symbol(cur); } } else { Fthrow(Qtype_error, const_list(false, 2, const_list(false, 2, Qlistp, Qsymbolp), symbol)); } return Qnil; } // recursively search all imports of SOURCE, looking for TARGET static bool check_recursive_import(LispVal *source, LispVal *target) { FOREACH(entry, ((LispPackage *) source)->imported) { if (HEAD(entry) == target || check_recursive_import(HEAD(entry), target)) { return true; } } return false; } DEFUN_DISTINGUISHED(import_package, "import-package", (LispVal * source, LispVal *names, LispVal *target), "(source names &opt target)", "Import each symbol with a name in NAMES from SOURCE into " "TARGET. TARGET defaults to the current package. If NAMES " "is t, import every symbol exported by SOURCE.") { LispPackage *target_pkg; if (target == Qunbound || NILP(target)) { target_pkg = refcount_ref(current_package); } else { target_pkg = (LispPackage *) normalize_package(target); } if (!PACKAGEP(target_pkg)) { refcount_unref(target_pkg); Fthrow(Qimport_error, Qnil); } if (names == Qunbound) { names = Qt; // all symbols } FOREACH(entry, target_pkg->imported) { if (HEAD(entry) == source) { if (names == Qt) { Fsettail(HEAD(entry), Qt); } else { LispVal *imported = TAIL(entry); // if we have already imported everything, do nothing if (imported == Qt) { goto done; } FOREACH(name, names) { if (SYMBOLP(name)) { name = LISPVAL(((LispSymbol *) name)->name); } else if (!STRINGP(name)) { refcount_unref(target_pkg); CHECK_TYPE(TYPE_STRING, name); } puthash(imported, name, Qt); } } goto done; } } // we didn't find any existing imports, so add a new entry if (check_recursive_import(source, LISPVAL(target_pkg))) { refcount_unref(target_pkg); Fthrow(Qimport_error, Qnil); } LispVal *lasttail = Qnil; FOREACH_TAIL(tail, target_pkg->imported) { if (NILP(TAIL(tail))) { lasttail = tail; break; } } if (LISTP(names)) { LispVal *norm_names = make_lisp_hashtable(Qstrings_equal, Qhash_string); FOREACH(name, names) { if (SYMBOLP(name)) { name = LISPVAL(((LispSymbol *) name)->name); } else if (!STRINGP(name)) { refcount_unref(target_pkg); refcount_unref(norm_names); CHECK_TYPE(TYPE_STRING, name); } puthash(norm_names, name, Qt); } names = norm_names; } if (NILP(lasttail)) { target_pkg->imported = const_list(false, 1, Fpair(source, names)); } else { Fsettail(lasttail, const_list(false, 1, Fpair(source, names))); } refcount_unref(names); done: refcount_unref(target_pkg); return Qnil; } DEFUN(find_package, "find-package", (LispVal * name), "(name)", "Return the package named NAME registered with the reader, if one " "exists.") { if (STRINGP(name)) { return Fgethash(package_table, name, Qnil); } else if (SYMBOLP(name)) { return Fgethash(package_table, LISPVAL(((LispSymbol *) name)->name), Qnil); } else if (PACKAGEP(name)) { LispPackage *pkg = (LispPackage *) name; LispVal *found = Fgethash(package_table, LISPVAL(pkg->name), Qnil); if (found == LISPVAL(pkg)) { return found; } else { refcount_unref(found); return Qnil; } } else { Fthrow(Qtype_error, Qnil); } } LispVal *find_package(const char *name, size_t length) { LispVal *sobj = make_lisp_string(name, length, false, false); LispVal *pkg = Ffind_package(sobj); refcount_unref(sobj); return pkg; } // #################### // # Symbol Functions # // #################### DEFUN(symbolp, "symbolp", (LispVal * val), "(obj)", "Return non-nil if OBJ is a symbol.") { return LISP_BOOL(SYMBOLP(val)); } DEFUN(keywordp, "keywordp", (LispVal * val), "(obj)", "Return non-nil if OBJ is a symbol interned in the kw package.") { return LISP_BOOL(KEYWORDP(val)); } DEFUN(const_value_p, "const-value-p", (LispVal * val), "(obj)", "Return non-nil if OBJ's value is constant.") { CHECK_TYPE(TYPE_SYMBOL, val); return LISP_BOOL(VALUE_CONSTANTP(val)); } DEFUN(const_func_p, "const-func-p", (LispVal * val), "(obj)", "Return non-nil if OBJ's value as a function is constant.") { CHECK_TYPE(TYPE_SYMBOL, val); return LISP_BOOL(FUNC_CONSTANTP(val)); } DEFUN(specialp, "specialp", (LispVal * val), "(obj)", "Return non-nil if OBJ is a special variable.") { CHECK_TYPE(TYPE_SYMBOL, val); return LISP_BOOL(SPECIALP(val)); } DEFUN(make_symbol, "make-symbol", (LispVal * name), "(name)", "Return a new uninterned symbol named NAME.") { return make_lisp_symbol(name); } DEFUN(make_symbol_special, "make-symbol-special", (LispVal * sym), "(sym)", "Make it so that SYM is a special symbol, that is, it is dynamically " "bound.") { CHECK_TYPE(TYPE_SYMBOL, sym); if (VALUE_CONSTANTP(sym)) { Fthrow(Qconstant_value_error, Fpair(sym, Qnil)); } ((LispSymbol *) sym)->is_special_var = true; return refcount_ref(sym); } DEFUN(symbol_package, "symbol-package", (LispVal * symbol), "(symbol)", "Return the package of SYMBOL.") { CHECK_TYPE(TYPE_SYMBOL, symbol); return refcount_ref(((LispSymbol *) symbol)->package); } DEFUN(symbol_name, "symbol-name", (LispVal * symbol), "(symbol)", "Return the name of SYMBOL.") { CHECK_TYPE(TYPE_SYMBOL, symbol); return refcount_ref(((LispSymbol *) symbol)->name); } DEFUN(symbol_function, "symbol-function", (LispVal * symbol, LispVal *resolve), "(symbol &opt resolve)", "Return the value as a function of SYMBOL. If RESOLVE is non-nil and the " "value is also a non-nil symbol, repeat this process.") { CHECK_TYPE(TYPE_SYMBOL, symbol); if (NILP(resolve)) { return refcount_ref(((LispSymbol *) symbol)->function); } while (SYMBOLP(symbol) && !NILP(symbol)) { symbol = ((LispSymbol *) symbol)->function; } return refcount_ref(symbol); } DEFUN(fset, "fset", (LispVal * sym, LispVal *new_func), "(symbol func)", "Set the value as a function of SYMBOL to FUNC.") { CHECK_TYPE(TYPE_SYMBOL, sym); LispSymbol *sobj = ((LispSymbol *) sym); if (FUNC_CONSTANTP(sobj)) { Fthrow(Qconstant_function_error, Fpair(sym, Qnil)); } refcount_ref(new_func); refcount_unref(sobj->function); sobj->function = new_func; return refcount_ref(new_func); } DEFUN(symbol_value, "symbol-value", (LispVal * symbol, LispVal *default_only), "(symbol &opt default-only)", "Return the global value of SYMBOL.") { CHECK_TYPE(TYPE_SYMBOL, symbol); if (KEYWORDP(symbol)) { return refcount_ref(symbol); } else if (SPECIALP(symbol) && NILP(default_only)) { LispVal *dynenv_entry = Fplist_assoc(the_stack->dynenv, symbol, Qnil); if (!NILP(dynenv_entry)) { return refcount_ref(HEAD(TAIL(dynenv_entry))); } } return refcount_ref(((LispSymbol *) symbol)->value); } DEFUN(set, "set", (LispVal * symbol, LispVal *value, LispVal *default_only), "(symbol value &opt default-only)", "Set the global value of SYMBOL to VALUE.") { CHECK_TYPE(TYPE_SYMBOL, symbol); if (VALUE_CONSTANTP(symbol)) { Fthrow(Qconstant_value_error, Fpair(symbol, Qnil)); } if (SPECIALP(symbol) && NILP(default_only)) { LispVal *dynenv_entry = Fplist_assoc(the_stack->dynenv, symbol, Qnil); if (!NILP(dynenv_entry)) { Fsethead(TAIL(dynenv_entry), value); return refcount_ref(value); } } LispSymbol *sobj = (LispSymbol *) symbol; refcount_unref(sobj->value); sobj->value = refcount_ref(value); return refcount_ref(value); } DEFUN(symbol_value_docstr, "symbol-value-docstr", (LispVal * symbol), "(symbol)", "Return the documentation for SYMBOL's value.") { CHECK_TYPE(TYPE_SYMBOL, symbol); return refcount_ref(((LispSymbol *) symbol)->value_doc); } DEFUN(set_symbol_value_docstr, "set-symbol-value-docstr", (LispVal * symbol, LispVal *docstr), "(symbol value)", "Set the documentation for SYMBOL's value.") { CHECK_TYPE(TYPE_SYMBOL, symbol); if (VALUE_CONSTANTP(symbol)) { Fthrow(Qconstant_value_error, Fpair(symbol, Qnil)); } LispSymbol *sobj = (LispSymbol *) symbol; refcount_unref(sobj->value_doc); sobj->value_doc = refcount_ref(docstr); return refcount_ref(docstr); } DEFUN(symbol_plist, "symbol-plist", (LispVal * symbol), "(symbol)", "Return the plist of SYMBOL.") { CHECK_TYPE(TYPE_SYMBOL, symbol); return refcount_ref(((LispSymbol *) symbol)->plist); } DEFUN(setplist, "setplist", (LispVal * symbol, LispVal *plist), "(symbol plist)", "Set the plist of SYMBOL to PLIST.") { CHECK_TYPE(TYPE_SYMBOL, symbol); LispSymbol *real = (LispSymbol *) symbol; refcount_unref(real->plist); real->plist = refcount_ref(plist); return Qnil; } DEFUN(exported_symbol_p, "exported-symbol-p", (LispVal * symbol), "(symbol)", "Return non-nil if SYMBOL is exported by its package.") { CHECK_TYPE(TYPE_SYMBOL, symbol); LispSymbol *sym = (LispSymbol *) symbol; if (NILP(sym->package)) { return Qnil; } else if (KEYWORDP(symbol)) { return Qt; } LispPackage *pkg = (LispPackage *) sym->package; return Fgethash(pkg->exported_sym_table, LISPVAL(sym), Qnil); } DEFUN(intern_soft, "intern-soft", (LispVal * name, LispVal *def, LispVal *package, LispVal *included_too), "(name &opt def package included-too)", "If a symbol named NAME is interned in PACKAGE, return it. Otherwise, " "return DEF. If INCLUDED-TOO is non-nil also check symbol imported by " "PACKAGE.") { LispPackage *real_pkg; if (NILP(package)) { real_pkg = refcount_ref(current_package); } else { real_pkg = (LispPackage *) normalize_package(package); } LispVal *cur = gethash(real_pkg->obarray, name, Qunbound); if (cur != Qunbound) { refcount_unref(real_pkg); return refcount_ref(cur); } if (!NILP(included_too)) { FOREACH(entry, real_pkg->imported) { if (TAIL(entry) != Qt) { LispVal *sub = HEAD(entry); LispVal *imported = TAIL(entry); if (!NILP(gethash(imported, name, Qnil))) { refcount_unref(real_pkg); // we import it from this package, so don't keep searching // if the intern fails return Fintern_soft(name, def, sub, Qt); } // otherwise, keep looking in a different package } else { cur = Fintern_soft(name, Qunbound, HEAD(entry), Qt); if (cur != Qunbound) { refcount_unref(real_pkg); return cur; } } } } refcount_unref(real_pkg); return refcount_ref(def); } DEFUN(intern, "intern", (LispVal * name, LispVal *package, LispVal *included_too), "(name &opt package included-too)", "If a SYMBOL named NAME is interned in PACKAGE, return it, otherwise, " "intern a new symbol into PACKAGE. If INCLUDED-TOO is non-nil, also " "search imported symbols of PACKAGE.") { CHECK_TYPE(TYPE_STRING, name); LispPackage *real_pkg; if (NILP(package)) { real_pkg = refcount_ref(current_package); } else { real_pkg = (LispPackage *) normalize_package(package); } LispVal *cur = Fintern_soft(name, Qunbound, package, included_too); if (cur != Qunbound) { return cur; } LispVal *sym = make_lisp_symbol(name); ((LispSymbol *) sym)->package = refcount_ref(real_pkg); puthash(real_pkg->obarray, name, sym); refcount_unref(real_pkg); return sym; } DEFUN(quote_symbol_name, "quote-symbol-name", (LispVal * name), "(name)", "Quote NAME such that it could be read back by the reader as a symbol " "called NAME.") { CHECK_TYPE(TYPE_STRING, name); LispString *str = (LispString *) name; size_t out_len = str->length; char *out = lisp_malloc(str->length + 1); for (size_t i = 0, oi = 0; i < str->length; ++i, ++oi) { char c = str->data[i]; if (c == ':' || c == '`' || c == ',' || c == '\'' || c == '"' || isspace(c) || c == '(' || c == ')' || c == '[' || c == ']') { out = lisp_realloc(out, ++out_len + 1); out[oi++] = '\\'; } out[oi] = str->data[i]; } out[out_len] = '\0'; return make_lisp_string(out, out_len, true, false); } DEFUN(symbol_accessible_p, "symbol-accessible-p", (LispVal * symbol, LispVal *package), "(symbol &opt package)", "Return non-nil if SYMBOL is interned in PACKAGE or transiently imported " "into it.") { LispVal *name = Fsymbol_name(symbol); LispVal *found; WITH_CLEANUP(name, { found = Fintern_soft(name, Qunbound, package, Qt); // }); bool res = found == symbol; refcount_unref(found); return LISP_BOOL(res); } DEF_STATIC_SYMBOL(kw_as_needed, "as-needed"); DEFUN(quote_symbol_for_read, "quote-symbol-for-read", (LispVal * target, LispVal *include_package, LispVal *from), "(target &opt include-package from)", "Quote TARGET, as symbol, such that a symbol with the same name as it " "will read by the reader. If INCLUDE-PACKAGE is :as-needed, add the " "package if it would be required to read back from the package FROM. If " "it is any other non-nil value, add the package regardless.") { CHECK_TYPE(TYPE_SYMBOL, target); LispSymbol *sym = (LispSymbol *) target; LispString *sym_name = (LispString *) Fquote_symbol_name(LISPVAL(sym->name)); if (KEYWORDP(target)) { size_t size = sym_name->length + 1; char *new_name = lisp_malloc(size + 1); snprintf(new_name, size + 1, ":%s", sym_name->data); refcount_unref(sym_name); return make_lisp_string(new_name, size, true, false); } else if (NILP(include_package)) { return LISPVAL(sym_name); } else if (include_package == Qkw_as_needed) { void *cl_handler = register_cleanup(&refcount_unref_as_callback, sym_name); bool accessible = !NILP(Fsymbol_accessible_p(LISPVAL(sym), from)); cancel_cleanup(cl_handler); if (accessible) { return LISPVAL(sym_name); } // otherwise, go on to print the package } if (NILP(sym->package)) { size_t size = sym_name->length + 2; char *new_name = lisp_malloc(size + 1); snprintf(new_name, size + 1, "::%s", sym_name->data); refcount_unref(sym_name); return make_lisp_string(new_name, size, true, false); } LispString *pkg_name = (LispString *) Fquote_symbol_name( LISPVAL(((LispPackage *) sym->package)->name)); if (NILP(Fexported_symbol_p(LISPVAL(sym)))) { size_t size = pkg_name->length + sym_name->length + 2; char *new_name = lisp_malloc(size + 1); snprintf(new_name, size + 1, "%s::%s", pkg_name->data, sym_name->data); refcount_unref(pkg_name); refcount_unref(sym_name); return make_lisp_string(new_name, size, true, false); } else { size_t size = pkg_name->length + sym_name->length + 1; char *new_name = lisp_malloc(size + 1); snprintf(new_name, size + 1, "%s:%s", pkg_name->data, sym_name->data); refcount_unref(pkg_name); refcount_unref(sym_name); return make_lisp_string(new_name, size, true, false); } } LispVal *intern(const char *name, size_t length, bool take, LispVal *package, bool included_too) { if (!NILP(package)) { CHECK_TYPE(TYPE_PACKAGE, package); } LispVal *name_obj = make_lisp_string((char *) name, length, take, false); LispVal *sym = Fintern(name_obj, package, LISP_BOOL(included_too)); refcount_unref(name_obj); return sym; } // ######################## // # Hash Table Functions # // ######################## DEFUN(hash_table_p, "hash-table-p", (LispVal * val), "(obj)", "Return non-nil if OBJ is a hash table.") { return LISP_BOOL(HASHTABLEP(val)); } DEFUN(make_hash_table, "make-hash-table", (LispVal * hash_fn, LispVal *eq_fn), "(&opt hash-fn eq-fn)", "Create a new hash table with hash function HASH-FN, defaulting to id, " "and equality test EQ-FN, defaulting to eq.") { return make_lisp_hashtable(eq_fn, hash_fn); } DEFUN(copy_hash_table, "copy-hash-table", (LispVal * table), "(table)", "Return a copy of TABLE.") { CHECK_TYPE(TYPE_HASHTABLE, table); LispHashtable *src = (LispHashtable *) table; CONSTRUCT_OBJECT(copy, LispHashtable, TYPE_HASHTABLE); copy->table_size = src->table_size; copy->count = src->count; copy->eq_fn = refcount_ref(src->eq_fn); copy->hash_fn = refcount_ref(src->hash_fn); copy->key_vals = lisp_malloc0(sizeof(struct HashtableEntry) * copy->table_size); HT_FOREACH_VALID_INDEX(src, i) { copy->key_vals[i].key = refcount_ref(src->key_vals[i].key); copy->key_vals[i].hash = src->key_vals[i].hash; copy->key_vals[i].value = refcount_ref(src->key_vals[i].value); } return LISPVAL(copy); } DEFUN(hash_table_count, "hash-table-count", (LispVal * table), "(table)", "Return the number of entries in TABLE.") { CHECK_TYPE(TYPE_HASHTABLE, table); return make_lisp_integer(((LispHashtable *) table)->count); } DEFUN(maphash, "maphash", (LispVal * func, LispVal *table), "(func table)", "Call FUNC for each key-value pair in TABLE. FUNC may modify table only " "by altering the current entry.") { HT_FOREACH_VALID_INDEX(table, i) { LispVal *args = const_list(true, 2, HASH_KEY(table, i), HASH_VALUE(table, i)); WITH_CLEANUP(args, { refcount_unref(Ffuncall(func, args)); // }); } return Qnil; } DEFUN( puthash, "puthash", (LispVal * table, LispVal *key, LispVal *value), "(table key value)", "Associate VALUE with KEY in TABLE, overriding any current association.") { return refcount_ref(puthash(table, key, value)); } DEFUN(gethash, "gethash", (LispVal * table, LispVal *key, LispVal *def), "(table key &opt def)", "Return the VALUE associated with KEY in table, or DEF is no such " "mapping exists.") { return refcount_ref(gethash(table, key, def)); } static bool hash_table_eq(LispVal *eq_fn, LispVal *v1, LispVal *v2) { if (NILP(eq_fn)) { return v1 == v2; } else if (eq_fn == Qstrings_equal) { return !NILP(Fstrings_equal(v1, v2)); } else { LispVal *eq_obj; LispVal *args = const_list(true, 2, v1, v2); WITH_CLEANUP(args, { eq_obj = Ffuncall(eq_fn, args); // }); bool result = !NILP(eq_obj); refcount_unref(eq_obj); return result; } } static uint64_t hash_table_hash(LispHashtable *self, LispVal *key) { if (NILP(self->hash_fn)) { return (uint64_t) key; } else if (self->hash_fn == Qhash_string) { // Make obarray and lexenv lookups faster LispVal *hash_obj = Fhash_string(key); uint64_t hash = ((LispInteger *) hash_obj)->value; refcount_unref(hash_obj); return hash; } else { LispVal *hash_obj; LispVal *args = const_list(true, 1, key); WITH_CLEANUP(args, { hash_obj = Ffuncall(self->hash_fn, args); // }); uint64_t hash; WITH_CLEANUP(hash_obj, { CHECK_TYPE(TYPE_INTEGER, hash_obj); hash = ((LispInteger *) hash_obj)->value; }); return hash; } } static ptrdiff_t hash_table_find_entry(struct HashtableEntry *entries, size_t size, LispVal *eq_fn, LispVal *key, uint64_t hash) { size_t i = hash % size; while (entries[i].key && !hash_table_eq(eq_fn, key, entries[i].key)) { i = (i + 1) % size; } return i; } DEFUN(remhash, "remhash", (LispVal * table, LispVal *key, LispVal *def), "(table key &opt def)", "Remove the value associated with KEY from TABLE. Return the removed " "value or DEF if no association was found.") { CHECK_TYPE(TYPE_HASHTABLE, table); LispHashtable *self = (LispHashtable *) table; uint64_t hash = hash_table_hash(self, key); ptrdiff_t i = hash_table_find_entry(self->key_vals, self->table_size, self->eq_fn, key, hash); if (HASH_SLOT_UNSET_P(self, i)) { return Qnil; } refcount_unref(self->key_vals[i].key); self->key_vals[i].key = NULL; LispVal *retval = self->key_vals[i].value; --self->count; // fixup the table for (size_t j = (i + 1) % self->table_size; !HASH_SLOT_UNSET_P(self, j); j = (j + 1) % self->table_size) { size_t k = HASH_HASH(self, j) % self->table_size; if ((i <= j && i < k && k <= j) || (i > j && (k <= j || i < k))) { // https://en.wikipedia.org/wiki/Open_addressing // test if the value actually should come before i or after j continue; } self->key_vals[i].hash = HASH_HASH(self, j); self->key_vals[i].key = HASH_KEY(self, j); self->key_vals[i].value = HASH_VALUE(self, j); self->key_vals[j].key = NULL; i = j; } return retval; } void free_hash_table_data_array(void *data) { struct HashtableDataArray *arr = data; for (size_t i = 0; i < arr->size; ++i) { refcount_unref(arr->entries[i].key); refcount_unref(arr->entries[i].value); } lisp_free(arr->entries); } // we assume the table is not full // return true if we added a new entry, false otherwise static bool puthash_to_array(LispVal *eq_fn, struct HashtableEntry *key_vals, size_t table_size, LispVal *key, uint64_t hash, LispVal *value) { ptrdiff_t i = hash_table_find_entry(key_vals, table_size, eq_fn, key, hash); if (!key_vals[i].key) { key_vals[i].key = refcount_ref(key); key_vals[i].hash = hash; key_vals[i].value = refcount_ref(value); return true; } else { refcount_unref(key_vals[i].key); key_vals[i].key = refcount_ref(key); refcount_unref(key_vals[i].value); key_vals[i].value = refcount_ref(value); return false; } } static void rehash_to(LispHashtable *self, size_t new_size) { struct HashtableEntry *new_data = lisp_malloc0(sizeof(struct HashtableEntry) * new_size); struct HashtableDataArray data_arr = {.size = new_size, .entries = new_data}; void *cl_handler; if (the_stack) { cl_handler = register_cleanup(&free_hash_table_data_array, &data_arr); } size_t new_count = 0; // this should be the same, but just in case the user // violates the rules of immutability HT_FOREACH_VALID_INDEX(self, i) { LispVal *key = HASH_KEY(self, i); uint64_t hash = HASH_HASH(self, i); LispVal *value = HASH_VALUE(self, i); if (puthash_to_array(self->eq_fn, new_data, new_size, key, hash, value)) { ++new_count; } } if (the_stack) { cancel_cleanup(cl_handler); } free_hash_table_data_array(&(struct HashtableDataArray) { .size = self->table_size, .entries = self->key_vals}); self->key_vals = new_data; self->table_size = new_size; self->count = new_count; } static inline void maybe_rehash(LispHashtable *self) { if (HASH_TABLE_LOAD_FACTOR(self) >= 0.5) { rehash_to(self, self->table_size * LISP_HASHTABLE_GROWTH_FACTOR); } /* else if (HASH_TABLE_LOAD_FACTOR(self) <= 0.1 && self->table_size > LISP_HASHTABLE_INITIAL_SIZE) { rehash_to(self, self->table_size / LISP_HASHTABLE_GROWTH_FACTOR); } */ } LispVal *puthash(LispVal *table, LispVal *key, LispVal *value) { CHECK_TYPE(TYPE_HASHTABLE, table); LispHashtable *self = (LispHashtable *) table; maybe_rehash(self); uint64_t hash = hash_table_hash(self, key); if (puthash_to_array(self->eq_fn, self->key_vals, self->table_size, key, hash, value)) { ++self->count; } return value; } LispVal *gethash(LispVal *table, LispVal *key, LispVal *def) { CHECK_TYPE(TYPE_HASHTABLE, table); assert(HASH_TABLE_LOAD_FACTOR(table) < 0.95); // infinite loop otherwise LispHashtable *self = (LispHashtable *) table; uint64_t hash = hash_table_hash(self, key); ptrdiff_t i = hash_table_find_entry(self->key_vals, self->table_size, self->eq_fn, key, hash); if (HASH_SLOT_UNSET_P(self, i)) { return def; } else { return HASH_VALUE(self, i); } } void remhash(LispVal *table, LispVal *key) { refcount_unref(Fremhash(table, key, Qnil)); } // ##################### // # Numeric Functions # // ##################### DEFUN(integerp, "integerp", (LispVal * val), "(obj)", "Return non-nil of OBJ is an integer.") { return LISP_BOOL(INTEGERP(val)); } DEFUN(floatp, "floatp", (LispVal * val), "(obj)", "Return non-nil if OBJ is a float.") { return LISP_BOOL(FLOATP(val)); } DEFUN(num_eq, "=", (LispVal * n1, LispVal *n2), "(n1 n2)", "Return non-nil if N1 and N2 are numerically equal.") { if (INTEGERP(n1) && INTEGERP(n2)) { return LISP_BOOL(((LispInteger *) n1)->value == ((LispInteger *) n2)->value); } else if (INTEGERP(n1) && FLOATP(n2)) { return LISP_BOOL(((LispInteger *) n1)->value == ((LispFloat *) n2)->value); } else if (FLOATP(n1) && INTEGERP(n2)) { return LISP_BOOL(((LispFloat *) n1)->value == ((LispInteger *) n2)->value); } else if (FLOATP(n1) && FLOATP(n2)) { return LISP_BOOL(((LispFloat *) n1)->value == ((LispFloat *) n2)->value); } else { Fthrow(Qtype_error, Qnil); } } DEFUN(num_gt, ">", (LispVal * n1, LispVal *n2), "(n1 n2)", "Return non-nil if N1 is greater than N2.") { if (INTEGERP(n1) && INTEGERP(n2)) { return LISP_BOOL(((LispInteger *) n1)->value > ((LispInteger *) n2)->value); } else if (INTEGERP(n1) && FLOATP(n2)) { return LISP_BOOL(((LispInteger *) n1)->value > ((LispFloat *) n2)->value); } else if (FLOATP(n1) && INTEGERP(n2)) { return LISP_BOOL(((LispFloat *) n1)->value > ((LispInteger *) n2)->value); } else if (FLOATP(n1) && FLOATP(n2)) { return LISP_BOOL(((LispFloat *) n1)->value > ((LispFloat *) n2)->value); } else { Fthrow(Qtype_error, Qnil); } } #define ONE_MATH_OPERAION(oper, out, n1, n2) \ if (INTEGERP(n1) && INTEGERP(n2)) { \ out = make_lisp_integer( \ ((LispInteger *) n1)->value oper((LispInteger *) n2)->value); \ } else if (INTEGERP(n1) && FLOATP(n2)) { \ out = make_lisp_float( \ ((LispInteger *) n1)->value oper((LispFloat *) n2)->value); \ } else if (FLOATP(n1) && INTEGERP(n2)) { \ out = make_lisp_float( \ ((LispFloat *) n1)->value oper((LispInteger *) n2)->value); \ } else if (FLOATP(n1) && FLOATP(n2)) { \ out = make_lisp_float( \ ((LispFloat *) n1)->value oper((LispFloat *) n2)->value); \ } else { \ Fthrow(Qtype_error, Qnil); \ } static inline LispVal *copy_number(LispVal *v) { if (FLOATP(v)) { return make_lisp_float(((LispFloat *) v)->value); } else if (INTEGERP(v)) { return make_lisp_integer(((LispInteger *) v)->value); } else { Fthrow(Qtype_error, Qnil); } } DEFUN(add, "+", (LispVal * args), "(&rest nums)", "Return the sum of NUMS.") { if (NILP(args)) { return make_lisp_integer(0); } LispVal *out = copy_number(HEAD(args)); FOREACH(arg, TAIL(args)) { LispVal *old_out = out; WITH_CLEANUP(old_out, { ONE_MATH_OPERAION(+, out, out, arg); // }); } return out; } DEFUN(sub, "-", (LispVal * args), "(&rest nums)", "Subtract from the first number in NUMS each other number in NUMS.") { if (NILP(args)) { return make_lisp_integer(0); } LispVal *out = copy_number(HEAD(args)); FOREACH(arg, TAIL(args)) { LispVal *old_out = out; WITH_CLEANUP(old_out, { ONE_MATH_OPERAION(-, out, out, arg); // }); } return out; } DEFUN(mul, "*", (LispVal * args), "(nums)", "Return the product of NUMS.") { if (NILP(args)) { return make_lisp_integer(1); } LispVal *out = copy_number(HEAD(args)); FOREACH(arg, TAIL(args)) { LispVal *old_out = out; WITH_CLEANUP(old_out, { ONE_MATH_OPERAION(*, out, out, arg); // }); } return out; } DEFUN(div, "/", (LispVal * first, LispVal *rest), "(first &rest rest)", "If REST is nil, return the reciprocal of FIRST. Otherwise, return FIRST " "divided by the product of REST.") { if (NILP(rest)) { if (INTEGERP(first)) { return make_lisp_float(1.0 / ((LispInteger *) first)->value); } else if (FLOATP(first)) { return make_lisp_float(1.0 / ((LispFloat *) first)->value); } else { Fthrow(Qtype_error, Qnil); } } LispVal *out = copy_number(HEAD(rest)); FOREACH(arg, TAIL(rest)) { LispVal *old_out = out; WITH_CLEANUP(old_out, { ONE_MATH_OPERAION(*, out, out, arg); // }); } if (FLOATP(first)) { LispVal *old_out = out; ONE_MATH_OPERAION(/, out, first, out); refcount_unref(old_out); } else if (INTEGERP(first)) { LispVal *old_out = out; LispVal *ff = make_lisp_float(((LispInteger *) first)->value); ONE_MATH_OPERAION(/, out, ff, out); refcount_unref(ff); refcount_unref(old_out); } else { refcount_unref(out); Fthrow(Qtype_error, Qnil); } return out; } // #################### // # Vector Functions # // #################### DEFUN(vectorp, "vectorp", (LispVal * val), "(obj)", "Return non-nil of OBJ is a vector.") { return LISP_BOOL(VECTORP(val)); } DEFUN(make_vector, "make-vector", (LispVal * initial_size, LispVal *initial_elem), "(initial-size &opt initial-elem)", "Return a new vector of INITIAL-SIZE with each element being " "INITIAL-ELEM.") { CHECK_TYPE(TYPE_INTEGER, initial_size); int64_t size = ((LispInteger *) initial_size)->value; if (size < 0) { Fthrow(Qout_of_bounds_error, const_list(true, 1, initial_size)); } LispVal **data = lisp_malloc(sizeof(LispVal *) * size); for (size_t i = 0; i < size; ++i) { data[i] = refcount_ref(initial_elem); } return make_lisp_vector(data, size); } DEFUN(vector, "vector", (LispVal * elems), "(&rest elems)", "Construct a vector form each of ELEMS.") { if (LISTP(elems)) { struct UnrefListData uld = {.vals = NULL, .len = 0}; WITH_PUSH_FRAME(Qnil, Qnil, true, { void *cl_handler = register_cleanup(&unref_free_list_double_ptr, &uld); FOREACH(elt, elems) { uld.vals = lisp_realloc(uld.vals, sizeof(LispVal *) * (++uld.len)); uld.vals[uld.len - 1] = elt; } cancel_cleanup(cl_handler); }); return make_lisp_vector(uld.vals, uld.len); } else if (STRINGP(elems)) { LispString *str = (LispString *) elems; LispVal **data = lisp_malloc(sizeof(LispVal *) * str->length); for (size_t i = 0; i < str->length; ++i) { data[i] = make_lisp_integer(str->data[i]); } return make_lisp_vector(data, str->length); } else if (VECTORP(elems)) { LispVector *vec = (LispVector *) elems; LispVal **data = lisp_malloc(sizeof(LispVal *) * vec->length); for (size_t i = 0; i < vec->length; ++i) { data[i] = refcount_ref(vec->data[i]); } return make_lisp_vector(data, vec->length); } else { Fthrow(Qtype_error, const_list(false, 3, const_list(false, 2, Qvectorp, Qstringp, Qlistp), refcount_ref(elems))); } } DEFUN(vector_length, "vector-length", (LispVal * vec), "(vec-or-str)", "Return the length of VEC-OR-STR, a vector or string.") { if (VECTORP(vec)) { return make_lisp_integer(((LispVector *) vec)->length); } else if (STRINGP(vec)) { return make_lisp_integer(((LispString *) vec)->length); } else { Fthrow(Qtype_error, const_list(false, 2, const_list(false, 2, Qvectorp, Qstringp), refcount_ref(vec))); } } DEFUN(aref, "aref", (LispVal * vec, LispVal *index), "(vec-or-str index)", "Return the element numbered INDEX in VEC-OR-STR, starting from zero.") { CHECK_TYPE(TYPE_INTEGER, index); int64_t idx = ((LispInteger *) index)->value; if (idx < 0) { Fthrow(Qout_of_bounds_error, const_list(true, 1, index)); } if (VECTORP(vec)) { LispVector *v = (LispVector *) vec; if (idx >= v->length) { Fthrow(Qout_of_bounds_error, const_list(true, 1, index)); } return refcount_ref(v->data[idx]); } else if (STRINGP(vec)) { LispString *s = (LispString *) vec; if (idx >= s->length) { Fthrow(Qout_of_bounds_error, const_list(true, 1, index)); } return make_lisp_integer(s->data[idx]); } else { Fthrow(Qtype_error, const_list(false, 2, const_list(false, 2, Qvectorp, Qstringp), refcount_ref(vec))); } } DEFUN(aset, "aset", (LispVal * vec, LispVal *index, LispVal *elem), "(vec index elem)", "Set the element at INDEX in VEC to ELEM.") { CHECK_TYPE(TYPE_INTEGER, index); CHECK_TYPE(TYPE_VECTOR, vec); int64_t idx = ((LispInteger *) index)->value; if (idx < 0) { Fthrow(Qout_of_bounds_error, const_list(true, 1, index)); } LispVector *v = (LispVector *) vec; if (idx >= v->length) { Fthrow(Qout_of_bounds_error, const_list(true, 1, index)); } refcount_unref(v->data[idx]); v->data[idx] = refcount_ref(elem); return refcount_ref(elem); } DEFUN(subvector, "subvector", (LispVal * seq, LispVal *start, LispVal *end), "(vec-or-str &opt start end)", "Return a sub-vector or sub-string of VEC-OR-STR between START and END, " "defaulting to 0 and the length of VEC-OR-STR.") { if (!NILP(start)) { CHECK_TYPE(TYPE_INTEGER, start); } if (!NILP(end)) { CHECK_TYPE(TYPE_INTEGER, end); } size_t length; if (VECTORP(seq)) { length = ((LispVector *) seq)->length; } else if (STRINGP(seq)) { length = ((LispString *) seq)->length; } else { Fthrow(Qtype_error, const_list(false, 2, const_list(false, 2, Qstringp, Qvectorp), seq)); } int64_t si = 0; if (!NILP(start)) { si = ((LispInteger *) start)->value; if (si < 0 || si > length) { Fthrow(Qout_of_bounds_error, const_list(true, 1, start)); } } int64_t se = 0; if (NILP(end)) { se = length; } else { se = ((LispInteger *) end)->value; if (se < 0 || se > length) { Fthrow(Qout_of_bounds_error, const_list(true, 1, end)); } else if (si > se) { Fthrow(Qout_of_bounds_error, const_list(true, 1, start)); } } if (VECTORP(seq)) { LispVector *vec = (LispVector *) seq; size_t subsize = se - si; LispVal **subarr = lisp_malloc(sizeof(LispVal *) * subsize); for (size_t i = si, sub_i = 0; i < se; ++i, ++sub_i) { subarr[sub_i] = refcount_ref(vec->data[i]); } return make_lisp_vector(subarr, subsize); } else { LispString *str = (LispString *) seq; size_t subsize = se - si; char *subarr = lisp_malloc(subsize); for (size_t i = si, sub_i = 0; i < se; ++i, ++sub_i) { subarr[sub_i] = str->data[i]; } return make_lisp_string(subarr, subsize, true, false); } } // #################### // # String Functions # // #################### DEFUN(stringp, "stringp", (LispVal * val), "(obj)", "Return non-nil if OBJ is a string.") { return LISP_BOOL(STRINGP(val)); } DEFUN(string, "string", (LispVal * val), "(seq)", "Convert SEQ—a string, vector, or list—to a string.") { if (STRINGP(val)) { return refcount_ref(val); } else if (VECTORP(val)) { LispVector *vec = (LispVector *) val; char *new_chars = lisp_malloc(vec->length); void *cl_handler = register_cleanup(&lisp_free, new_chars); for (size_t i = 0; i < vec->length; ++i) { CHECK_TYPE(TYPE_INTEGER, vec->data[i]); LispInteger *elt = (LispInteger *) vec->data[i]; if (elt->value < -128 || elt->value > 127) { Fthrow(Qtype_error, Qnil); } new_chars[i] = elt->value; } cancel_cleanup(cl_handler); return make_lisp_string(new_chars, vec->length, true, false); } else if (PAIRP(val)) { char *retval; size_t size = 0; WITH_PUSH_FRAME(Qnil, Qnil, true, { char *new_data = NULL; void *cl_handler = register_cleanup(&free_double_ptr, &new_data); FOREACH(elt, val) { CHECK_TYPE(TYPE_INTEGER, elt); LispInteger *i = (LispInteger *) elt; if (i->value < -128 || i->value > 127) { Fthrow(Qtype_error, Qnil); } new_data = lisp_realloc(new_data, sizeof(char) * ++size); new_data[size - 1] = i->value; } cancel_cleanup(cl_handler); retval = new_data; }); return make_lisp_string(retval, size, true, false); } else { Fthrow(Qtype_error, const_list(false, 2, const_list(false, 3, Qvectorp, Qstringp, Qlistp), refcount_ref(val))); } } DEFUN(hash_string, "hash-string", (LispVal * obj), "(str)", "Return the hash of STR.") { CHECK_TYPE(TYPE_STRING, obj); const char *str = ((LispString *) obj)->data; uint64_t hash = 5381; int c; while ((c = *(str++))) { hash = ((hash << 5) + hash) + c; } return make_lisp_integer(hash); } DEFUN(strings_equal, "strings-equal", (LispVal * obj1, LispVal *obj2), "(str1 str2)", "Return non-nil if STR1 and STR2 are equal.") { CHECK_TYPE(TYPE_STRING, obj1); CHECK_TYPE(TYPE_STRING, obj2); LispString *str1 = (LispString *) obj1; LispString *str2 = (LispString *) obj2; if (str1->length != str2->length) { return Qnil; } return LISP_BOOL(memcmp(str1->data, str2->data, str1->length) == 0); } DEFUN(string_to_vector, "string-to-vector", (LispVal * str), "(str)", "Convert STR to a vector.") { CHECK_TYPE(TYPE_STRING, str); LispString *s = (LispString *) str; LispVal **vdata = lisp_malloc(sizeof(LispVal *) * s->length); for (size_t i = 0; i < s->length; ++i) { vdata[i] = make_lisp_integer(s->data[i]); } return make_lisp_vector(vdata, s->length); } DEFUN(quote_string, "quote-string", (LispVal * target), "(target)", "Quote TARGET such that it can be read back by the reader.") { CHECK_TYPE(TYPE_STRING, target); LispString *str = (LispString *) target; size_t out_size = str->length + 2; char *out = lisp_malloc(out_size + 1); out[0] = '"'; for (size_t i = 0, oi = 1; i < str->length; ++i, ++oi) { switch (str->data[i]) { case '\n': out = lisp_realloc(out, ++out_size + 1); out[oi++] = '\\'; out[oi] = 'n'; break; case '\t': out = lisp_realloc(out, ++out_size + 1); out[oi++] = '\\'; out[oi] = 't'; break; case '\r': out = lisp_realloc(out, ++out_size + 1); out[oi++] = '\\'; out[oi] = 'r'; break; case '\0': out = lisp_realloc(out, ++out_size + 1); out[oi++] = '\\'; out[oi] = '0'; break; case '"': out = lisp_realloc(out, ++out_size + 1); out[oi++] = '\\'; out[oi] = '"'; break; case '\\': out = lisp_realloc(out, ++out_size + 1); out[oi++] = '\\'; out[oi] = '\\'; break; default: out[oi] = str->data[i]; break; } } out[out_size - 1] = '"'; out[out_size] = '\0'; return make_lisp_string(out, out_size, true, false); } DEFUN(concat, "concat", (LispVal * strings), "(&rest strings)", "Concatenate each string in STRINGS.") { LispVal *retval; WITH_PUSH_FRAME(Qnil, Qnil, true, { char *out = lisp_malloc(1); out[0] = '\0'; size_t size = 0; void *cl_handler = register_cleanup(&free_double_ptr, &out); FOREACH(elt, strings) { if (NILP(elt)) { continue; } CHECK_TYPE(TYPE_STRING, elt); LispString *s = (LispString *) elt; size += s->length; out = lisp_realloc(out, size + 1); strncat(out, s->data, s->length); } cancel_cleanup(cl_handler); retval = make_lisp_string(out, size, true, false); }); return retval; } LispVal *sprintf_lisp(const char *format, ...) { va_list args; va_start(args, format); va_list args_measure; va_copy(args_measure, args); int size = vsnprintf(NULL, 0, format, args_measure) + 1; va_end(args_measure); char *buffer = lisp_malloc(size); vsnprintf(buffer, size, format, args); LispVal *obj = make_lisp_string(buffer, size - 1, true, false); va_end(args); return obj; } bool strings_equal_nocase(const char *s1, const char *s2, size_t n) { for (size_t i = 0; i < n; ++i) { if (!s1[i] || !s2[i]) { return !s1[i] && !s2[i]; } else if (tolower(s1[i]) != tolower(s2[i])) { return false; } } return true; } // #################### // # Record Functions # // #################### DEFUN(recordp, "recordp", (LispVal * val), "(obj)", "Return non-nil if OBJ is a record.") { return LISP_BOOL(RECORDP(val)); } DEFUN_DISTINGUISHED(make_record, "make-record", (LispVal * type, LispVal *length), "(type length &opt native-backing)", "Return a new record object of TYPE.") { CHECK_TYPE(TYPE_INTEGER, length); int64_t real_length = ((LispInteger *) length)->value; if (real_length < 0) { Fthrow(Qout_of_bounds_error, Qnil); } return make_lisp_record(type, real_length); } DEFUN(record_function, "record-function", (LispVal * record), "(record)", "Return the function associated with RECORD.") { CHECK_TYPE(TYPE_RECORD, record); return refcount_ref(((LispRecord *) record)->function); } DEFUN(set_record_function, "set-record-function", (LispVal * record, LispVal *value), "(record value)", "Set the function of RECORD to VALUE.") { CHECK_TYPE(TYPE_RECORD, record); LispRecord *rec = (LispRecord *) record; refcount_unref(rec->function); rec->function = refcount_ref(value); return refcount_ref(value); } DEFUN(record_length, "record-length", (LispVal * record), "(record)", "Return the length of RECORD.") { CHECK_TYPE(TYPE_RECORD, record); return make_lisp_integer(((LispRecord *) record)->length); } DEFUN(record_slot, "record-slot", (LispVal * record, LispVal *index), "(obj index)", "Return the INDEXth slot of RECORD.") { CHECK_TYPE(TYPE_RECORD, record); CHECK_TYPE(TYPE_INTEGER, index); LispRecord *rec = (LispRecord *) record; int64_t real_index = ((LispInteger *) index)->value; if (real_index < 0 || real_index > rec->length) { Fthrow(Qout_of_bounds_error, Fpair(index, Qnil)); } return refcount_ref(rec->data[real_index]); } DEFUN(set_record_slot, "set-record-slot", (LispVal * record, LispVal *index, LispVal *value), "(record index value)", "Set the INDEXth slot of RECORD to VALUE.") { CHECK_TYPE(TYPE_RECORD, record); CHECK_TYPE(TYPE_INTEGER, index); LispRecord *rec = (LispRecord *) record; int64_t real_index = ((LispInteger *) index)->value; if (real_index < 0 || real_index > rec->length) { Fthrow(Qout_of_bounds_error, Fpair(index, Qnil)); } refcount_unref(rec->data[real_index]); rec->data[real_index] = refcount_ref(value); return Qnil; } // ################ // # IO Functions # // ################ static inline int CHECK_IO_RESULT(int res, int fd) { if (res < 0) { if (errno == EBADFD) { Fthrow(Qtype_error, const_list(false, 1, make_lisp_integer(fd))); } else { Fthrow(Qio_error, Qnil); } } return res; } static int64_t internal_print(void *obj, int64_t fd, bool readably, bool first_in_list) { switch (TYPEOF(obj)) { case TYPE_STRING: { if (readably) { LispVal *quoted = Fquote_string(obj); int64_t rval = 0; WITH_CLEANUP(quoted, { rval = internal_print(quoted, fd, false, true); // }); return rval; } else { LispString *str = obj; return CHECK_IO_RESULT(write(fd, str->data, str->length), fd); } } case TYPE_SYMBOL: { LispVal *name = Qnil; if (readably) { name = Fquote_symbol_for_read(obj, Qkw_as_needed, Qnil); } else { name = refcount_ref(((LispSymbol *) obj)->name); } int64_t np; WITH_CLEANUP(name, { np = internal_print(name, fd, false, true); // }); return np; } break; case TYPE_PAIR: { if (HEAD(obj) == Qquote && PAIRP(TAIL(obj)) && NILP(TAIL(TAIL(obj)))) { int64_t np = CHECK_IO_RESULT(dprintf(fd, "'"), fd); np += internal_print(HEAD(TAIL(obj)), fd, readably, true); return np; } int64_t np; if (first_in_list) { np = CHECK_IO_RESULT(dprintf(fd, "("), fd); } else { np = CHECK_IO_RESULT(dprintf(fd, " "), fd); } np += internal_print(HEAD(obj), fd, readably, true); if (TAIL(obj) == Qnil) { np = CHECK_IO_RESULT(dprintf(fd, ")"), fd); } else { np += internal_print(TAIL(obj), fd, readably, false); } return np; } case TYPE_VECTOR: { LispVector *v = obj; int64_t np = CHECK_IO_RESULT(dprintf(fd, "["), fd); for (size_t i = 0; i < v->length; ++i) { np += internal_print(v->data[i], fd, readably, true); if (i != v->length - 1) { np += CHECK_IO_RESULT(dprintf(fd, " "), fd); } } np += CHECK_IO_RESULT(dprintf(fd, "]"), fd); return np; } case TYPE_INTEGER: return CHECK_IO_RESULT( dprintf(fd, "%ji", (intmax_t) ((LispInteger *) obj)->value), fd); case TYPE_FLOAT: return CHECK_IO_RESULT(dprintf(fd, "%Lf", ((LispFloat *) obj)->value), fd); case TYPE_FUNCTION: { LispFunction *fn = obj; int64_t np; bool need_name = true; if (fn->is_builtin && fn->is_macro) { np = CHECK_IO_RESULT(dprintf(fd, "is_builtin) { np = CHECK_IO_RESULT(dprintf(fd, "is_macro && fn->name == Qlambda) { np = CHECK_IO_RESULT(dprintf(fd, "is_macro) { np = CHECK_IO_RESULT(dprintf(fd, "name == Qlambda) { np = CHECK_IO_RESULT(dprintf(fd, "name, fd, readably, true); np += CHECK_IO_RESULT(dprintf(fd, " "), fd); } np += CHECK_IO_RESULT(dprintf(fd, "at %#jx>", (uintmax_t) obj), fd); return np; } case TYPE_HASHTABLE: { LispHashtable *ht = obj; LispVal *hash_fn = NILP(ht->hash_fn) ? Qid : ht->hash_fn; LispVal *eq_fn = NILP(ht->eq_fn) ? Qeq : ht->eq_fn; int64_t np = CHECK_IO_RESULT( dprintf(fd, "", (uintmax_t) obj), fd); return np; } case TYPE_USER_POINTER: return CHECK_IO_RESULT(dprintf(fd, "", (uintmax_t) USERPTR(void *, obj), (uintmax_t) obj), fd); case TYPE_PACKAGE: { LispPackage *pkg = obj; int64_t np = CHECK_IO_RESULT(dprintf(fd, "name)); WITH_CLEANUP(name_str, { np += internal_print(name_str, fd, readably, true); // }); np += CHECK_IO_RESULT( dprintf(fd, " interned=%ju at %#jx>", (uintmax_t) ((LispHashtable *) pkg->obarray)->count, (uintmax_t) obj), fd); return np; } break; case TYPE_RECORD: { // TODO implement LispRecord *rec = (LispRecord *) obj; int64_t np = CHECK_IO_RESULT( dprintf(fd, "<%srecord type=", NILP(rec->function) ? "" : "callable-"), fd); np += internal_print(rec->record_type, fd, readably, true); np += CHECK_IO_RESULT(dprintf(fd, " length=%ju at %#jx>", (uintmax_t) rec->length, (uintmax_t) obj), fd); return np; } break; default: abort(); } } DEFUN_DISTINGUISHED(print, "print", (LispVal * obj, LispVal *readably, LispVal *stream), "(obj &opt readably stream)", "Write a human readable representation of OBJ to STREAM, " "defaulting to the standard output. With READABLY non-nil, " "print OBJ in a way that it can be read back.") { int64_t fd; if (stream == Qunbound) { fd = 1; } else { CHECK_TYPE(TYPE_INTEGER, stream); fd = ((LispInteger *) stream)->value; if (fd < 0) { Fthrow(Qtype_error, const_list(true, 1, stream)); } } bool readably_bool = readably != Qunbound && !NILP(readably); return make_lisp_integer(internal_print(obj, fd, readably_bool, true)); } DEFUN_DISTINGUISHED( println, "println", (LispVal * obj, LispVal *readably, LispVal *stream), "(obj &opt readably stream)", "Call print with OBJ and STREAM, then write a newline to STREAM. With " "READABLY non-nil, print OBJ in a way that it can be read back.") { static char NEWLINE = '\n'; int64_t fd; if (stream == Qunbound) { fd = 1; } else { CHECK_TYPE(TYPE_INTEGER, stream); fd = ((LispInteger *) stream)->value; if (fd < 0) { Fthrow(Qtype_error, const_list(true, 1, stream)); } } int64_t np = 0; if (obj != Qunbound) { bool readably_bool = readably != Qunbound && !NILP(readably); np += internal_print(obj, fd, readably_bool, true); } np += CHECK_IO_RESULT(write(fd, &NEWLINE, 1), fd); fsync(fd); return make_lisp_integer(np); } // ######################## // # Lexenv and the Stack # // ######################## DEF_STATIC_SYMBOL(kw_success, "success"); DEF_STATIC_SYMBOL(kw_finally, "finally"); DEFUN(backtrace, "backtrace", (void), "()", "Return a backtrace.") { LispVal *head = Qnil; LispVal *end = Qnil; for (StackFrame *frame = the_stack; frame; frame = frame->next) { if (frame->hidden) { continue; } if (NILP(head)) { head = Fpair(Fpair(LISPVAL(frame->name), frame->detail), Qnil); refcount_unref(HEAD(head)); end = head; } else { LispVal *new_end = Fpair(Fpair(LISPVAL(frame->name), frame->detail), Qnil); refcount_unref(HEAD(new_end)); Fsettail(end, new_end); refcount_unref(new_end); end = new_end; } } return head; } #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Winfinite-recursion" DEFUN(throw, "throw", (LispVal * signal, LispVal *rest), "(signal &rest rest)", "Throw a signal SIGNAL with data REST.") { CHECK_TYPE(TYPE_SYMBOL, signal); LispVal *error_arg = const_list(false, 2, Fpair(signal, rest), Fbacktrace()); while (the_stack) { if (!the_stack->enable_handlers) { goto up_frame; } LispVal *handler = gethash(LISPVAL(the_stack->handlers), signal, Qunbound); if (handler == Qunbound) { // handler for all exceptions handler = gethash(LISPVAL(the_stack->handlers), Qt, Qunbound); } if (handler != Qunbound) { the_stack->enable_handlers = false; LispVal *var = HEAD(handler); LispVal *form = TAIL(handler); WITH_PUSH_FRAME(Qnil, Qnil, true, { WITH_CLEANUP(error_arg, { if (!NILP(var)) { CHECK_TYPE(TYPE_SYMBOL, var); if (VALUE_CONSTANTP(var)) { Fthrow(Qconstant_value_error, Fpair(var, Qnil)); } push_to_lexenv(&the_stack->lexenv, var, error_arg); } stack_return = Feval(form, the_stack->lexenv); // }); }); longjmp(the_stack->start, STACK_EXIT_THROW); } up_frame: { // steal the form so we can call it after we unwind (in case it // throws) LispVal *unwind_form = the_stack->unwind_form; the_stack->unwind_form = Qnil; stack_leave(); if (!NILP(unwind_form)) { void *cl_handler = register_cleanup(&refcount_unref_as_callback, error_arg); WITH_CLEANUP(unwind_form, { refcount_unref(Feval(unwind_form, the_stack->lexenv)); // }); cancel_cleanup(cl_handler); } } } fprintf(stderr, "ERROR: An exception has propagated past the top of the stack!\n"); fprintf(stderr, "Type: "); debug_dump(stderr, signal, true); fprintf(stderr, "Args: "); debug_dump(stderr, rest, true); fprintf(stderr, "Lisp will now exit..."); // we never used it, so drop it refcount_unref(error_arg); abort(); } #pragma GCC diagnostic pop StackFrame *the_stack = NULL; LispVal *stack_return = NULL; DEF_STATIC_SYMBOL(toplevel, "toplevel"); void stack_enter(LispVal *name, LispVal *detail, bool inherit) { StackFrame *frame = lisp_malloc(sizeof(StackFrame)); frame->name = name; frame->return_tag = Qnil; frame->hidden = true; frame->detail = detail; frame->lexenv = Qnil; if (inherit && the_stack) { frame->lexenv = refcount_ref(the_stack->lexenv); } frame->dynenv = the_stack ? refcount_ref(the_stack->dynenv) : Qnil; frame->enable_handlers = true; frame->handlers = make_lisp_hashtable(Qnil, Qnil); frame->unwind_form = Qnil; frame->cleanup_handlers = NULL; frame->next = the_stack; the_stack = frame; } void stack_leave(void) { StackFrame *frame = the_stack; the_stack = the_stack->next; refcount_unref(frame->name); refcount_unref(frame->return_tag); refcount_unref(frame->detail); refcount_unref(frame->lexenv); refcount_unref(frame->dynenv); refcount_unref(frame->handlers); while (frame->cleanup_handlers) { frame->cleanup_handlers->fun(frame->cleanup_handlers->data); struct CleanupHandlerEntry *next = frame->cleanup_handlers->next; lisp_free(frame->cleanup_handlers); frame->cleanup_handlers = next; } LispVal *unwind_form = frame->unwind_form; // steal the ref frame->unwind_form = Qnil; lisp_free(frame); if (!NILP(unwind_form)) { WITH_CLEANUP(unwind_form, { refcount_unref(Feval(unwind_form, the_stack->lexenv)); // }) } } void *register_cleanup(lisp_cleanup_func_t fun, void *data) { struct CleanupHandlerEntry *entry = lisp_malloc(sizeof(struct CleanupHandlerEntry)); entry->fun = fun; entry->data = data; entry->next = the_stack->cleanup_handlers; the_stack->cleanup_handlers = entry; return entry; } void free_double_ptr(void *ptr) { lisp_free(*(void **) ptr); } void unref_free_list_double_ptr(void *ptr) { struct UnrefListData *data = ptr; for (size_t i = 0; i < data->len; ++i) { refcount_unref(data->vals[i]); } lisp_free(data->vals); } void unref_double_ptr(void *ptr) { if (*(void **) ptr) { refcount_unref(*(void **) ptr); *(void **) ptr = NULL; } } void cancel_cleanup(void *handle) { struct CleanupHandlerEntry *entry = the_stack->cleanup_handlers; if (entry == handle) { the_stack->cleanup_handlers = entry->next; lisp_free(entry); } else { while (entry) { if (entry->next == handle) { struct CleanupHandlerEntry *to_free = entry->next; entry->next = entry->next->next; lisp_free(to_free); break; } entry = entry->next; } } } // ######################### // # Errors and Conditions # // ######################### DEF_STATIC_SYMBOL(shutdown_signal, "shutdown-signal"); DEF_STATIC_SYMBOL(error, "error"); DEF_STATIC_SYMBOL(type_error, "type-error"); DEF_STATIC_SYMBOL(read_error, "read-error"); DEF_STATIC_SYMBOL(unclosed_error, "read-error"); DEF_STATIC_SYMBOL(constant_function_error, "constant-function-error"); DEF_STATIC_SYMBOL(constant_value_error, "constant-value-error"); DEF_STATIC_SYMBOL(eof_error, "eof-error"); DEF_STATIC_SYMBOL(void_variable_error, "void-variable-error"); DEF_STATIC_SYMBOL(void_function_error, "void-function-error"); DEF_STATIC_SYMBOL(circular_error, "circular-error"); DEF_STATIC_SYMBOL(malformed_lambda_list_error, "malformed-lambda-list-error"); DEF_STATIC_SYMBOL(argument_error, "argument-error"); DEF_STATIC_SYMBOL(invalid_function_error, "invalid-function-error"); DEF_STATIC_SYMBOL(no_applicable_method_error, "no-applicable-method-error"); DEF_STATIC_SYMBOL(return_frame_error, "return-frame-error"); DEF_STATIC_SYMBOL(package_exists_error, "package-exists-error"); DEF_STATIC_SYMBOL(import_error, "import-error"); DEF_STATIC_SYMBOL(unknown_package_error, "unknown-package-error"); DEF_STATIC_SYMBOL(out_of_bounds_error, "out-of-bounds-error"); DEF_STATIC_SYMBOL(io_error, "io-error"); // ################### // # Debug Functions # // ################### static void debug_dump_real(FILE *stream, void *obj, bool first) { switch (TYPEOF(obj)) { case TYPE_STRING: { LispString *str = (LispString *) obj; // TODO actually quote fputc('"', stream); fwrite(str->data, 1, str->length, stream); fputc('"', stream); } break; case TYPE_SYMBOL: { LispSymbol *sym = (LispSymbol *) obj; if (KEYWORDP(obj)) { fputc(':', stream); } else if (NILP(sym->package)) { fprintf(stream, "::"); } else if (sym->package != current_package) { LispPackage *pkg = (LispPackage *) sym->package; fwrite(pkg->name->data, 1, pkg->name->length, stream); fputc(':', stream); if (NILP(Fexported_symbol_p(obj))) { fputc(':', stream); } } fwrite(sym->name->data, 1, sym->name->length, stream); } break; case TYPE_PAIR: { LispPair *pair = (LispPair *) obj; if (first) { fputc('(', stream); } else { fputc(' ', stream); } debug_dump_real(stream, pair->head, true); if (NILP(pair->tail)) { fputc(')', stream); } else if (PAIRP(pair->tail)) { debug_dump_real(stream, pair->tail, false); } else { fprintf(stream, " . "); debug_dump_real(stream, pair->tail, false); fputc(')', stream); } } break; case TYPE_INTEGER: fprintf(stream, "%jd", (intmax_t) ((LispInteger *) obj)->value); break; case TYPE_FLOAT: fprintf(stream, "%Lf", ((LispFloat *) obj)->value); break; case TYPE_VECTOR: { LispVector *vec = (LispVector *) obj; fputc('[', stream); for (size_t i = 0; i < vec->length; ++i) { if (i) { fputc(' ', stream); } debug_dump_real(stream, vec->data[i], true); } fputc(']', stream); } break; case TYPE_FUNCTION: { LispFunction *fobj = obj; LispVal *name = ((LispFunction *) obj)->name; if (fobj->is_builtin) { fprintf(stream, "is_macro) { fprintf(stream, "", (uintmax_t) obj); } break; case TYPE_HASHTABLE: { LispHashtable *tbl = (LispHashtable *) obj; fprintf(stream, "", tbl->table_size, tbl->count, (uintmax_t) obj); } break; case TYPE_USER_POINTER: { LispUserPointer *ptr = (LispUserPointer *) obj; fprintf(stream, "", (uintmax_t) ptr->data, (uintmax_t) obj); } break; case TYPE_PACKAGE: { LispPackage *pkg = (LispPackage *) obj; fprintf(stream, "name->data, 1, pkg->name->length, stream); fprintf(stream, " obarray-size=%zu at %#jx>", ((LispHashtable *) pkg->obarray)->count, (uintmax_t) obj); } break; case TYPE_RECORD: { LispRecord *rec = (LispRecord *) obj; fprintf(stream, "<%srecord type=", NILP(rec->function) ? "" : "callable-"); debug_dump_real(stream, rec->record_type, true); fprintf(stream, " length=%ju at %#jx>", (uintmax_t) rec->length, (uintmax_t) obj); } break; default: fprintf(stream, "", (uintmax_t) LISPVAL(obj)->type, (uintmax_t) obj); break; } } void debug_dump(FILE *stream, void *obj, bool newline) { debug_dump_real(stream, obj, true); if (newline) { fputc('\n', stream); } } void debug_print_hashtable(FILE *stream, LispVal *table) { debug_dump(stream, table, true); HT_FOREACH_VALID_INDEX(table, i) { fprintf(stream, "- "); debug_dump(stream, HASH_KEY(table, i), false); fprintf(stream, " = "); debug_dump(stream, HASH_VALUE(table, i), true); } } static bool debug_print_tree_callback(void *obj, const RefcountList *trail, void *stream_raw) { FILE *stream = stream_raw; size_t depth = refcount_list_length(trail); for (size_t i = 0; i < depth; ++i) { fprintf(stream, " "); } fprintf(stream, "- "); debug_dump(stream, obj, true); return false; } void debug_print_tree(FILE *stream, void *obj) { refcount_debug_walk_tree(obj, debug_print_tree_callback, stream); } // ################ // # Registration # // ################ static void register_symbols_and_functions(void) { // don't intern Qunbound! REGISTER_DO_INTERN(nil, system_package); REGISTER_DO_INTERN(t, system_package); REGISTER_SYMBOL(macro); REGISTER_SYMBOL(opt); REGISTER_SYMBOL(allow_other_keys); REGISTER_SYMBOL(key); REGISTER_SYMBOL(rest); REGISTER_SYMBOL(declare); REGISTER_SYMBOL(name); REGISTER_SYMBOL(no_backtrace); REGISTER_SYMBOL(comma); REGISTER_SYMBOL(comma_at); REGISTER_SYMBOL(backquote); REGISTER_SYMBOL(symbol); REGISTER_SYMBOL(integer); REGISTER_SYMBOL(float); REGISTER_SYMBOL(function); REGISTER_SYMBOL(hash_table); REGISTER_SYMBOL(user_pointer); REGISTER_SYMBOL(package); REGISTER_SYMBOL(record); REGISTER_KEYWORD(kw_success); REGISTER_KEYWORD(kw_finally); REGISTER_KEYWORD(kw_as_needed); REGISTER_SYMBOL(shutdown_signal); REGISTER_SYMBOL(error); REGISTER_SYMBOL(type_error); REGISTER_SYMBOL(read_error); REGISTER_SYMBOL(eof_error); REGISTER_SYMBOL(unclosed_error); REGISTER_SYMBOL(constant_function_error); REGISTER_SYMBOL(constant_value_error); REGISTER_SYMBOL(void_variable_error); REGISTER_SYMBOL(void_function_error); REGISTER_SYMBOL(circular_error); REGISTER_SYMBOL(malformed_lambda_list_error); REGISTER_SYMBOL(argument_error); REGISTER_SYMBOL(invalid_function_error); REGISTER_SYMBOL(no_applicable_method_error); REGISTER_SYMBOL(return_frame_error); REGISTER_SYMBOL(package_exists_error); REGISTER_SYMBOL(import_error); REGISTER_SYMBOL(unknown_package_error); REGISTER_SYMBOL(out_of_bounds_error); REGISTER_SYMBOL(io_error); REGISTER_SYMBOL(toplevel); // some functions that mustn't be user accessible REGISTER_STATIC_FUNCTION(set_for_return); REGISTER_STATIC_FUNCTION(internal_real_return); // ############################### // # General and Misc. Functions # // ############################### REGISTER_FUNCTION(exit); REGISTER_FUNCTION(id); REGISTER_FUNCTION(eq); REGISTER_FUNCTION(equal); REGISTER_FUNCTION(breakpoint); REGISTER_FUNCTION(not); REGISTER_FUNCTION(type_of); REGISTER_FUNCTION(user_pointer_p); REGISTER_FUNCTION(callablep); REGISTER_FUNCTION(native_type_p); // ################################## // # Evaluation and Macro Expansion # // ################################## REGISTER_FUNCTION(eval); REGISTER_FUNCTION(funcall); REGISTER_FUNCTION(apply); REGISTER_FUNCTION(macroexpand_1); REGISTER_FUNCTION(macroexpand_toplevel); REGISTER_FUNCTION(macroexpand_all); // ################# // # Special Forms # // ################# REGISTER_FUNCTION(quote); REGISTER_FUNCTION(if); REGISTER_FUNCTION(setq); REGISTER_FUNCTION(progn); REGISTER_FUNCTION(condition_case); REGISTER_FUNCTION(lambda); REGISTER_FUNCTION(while); REGISTER_FUNCTION(and); REGISTER_FUNCTION(or); REGISTER_FUNCTION(in_package); REGISTER_FUNCTION(return_from); // ###################### // # Function Functions # // ###################### REGISTER_FUNCTION(functionp); REGISTER_FUNCTION(macrop); REGISTER_FUNCTION(builtinp); REGISTER_FUNCTION(special_form_p); REGISTER_FUNCTION(function_docstr); REGISTER_FUNCTION(function_properties); // ########################### // # Pair and List Functions # // ########################### REGISTER_FUNCTION(pairp); REGISTER_FUNCTION(atom); REGISTER_FUNCTION(pair); REGISTER_FUNCTION(head); REGISTER_FUNCTION(tail); REGISTER_FUNCTION(sethead); REGISTER_FUNCTION(settail); // lists REGISTER_FUNCTION(listp); REGISTER_FUNCTION(list_length); REGISTER_FUNCTION(copy_list); REGISTER_FUNCTION(copy_tree); // plists REGISTER_FUNCTION(plist_get); REGISTER_FUNCTION(plist_set); REGISTER_FUNCTION(plist_rem); REGISTER_FUNCTION(plist_assoc); // ##################### // # Package Functions # // ##################### REGISTER_FUNCTION(packagep); REGISTER_FUNCTION(make_package); REGISTER_FUNCTION(package_name); REGISTER_FUNCTION(register_package); REGISTER_FUNCTION(current_package); REGISTER_FUNCTION(set_current_package); REGISTER_FUNCTION(mapsymbols); REGISTER_FUNCTION(export_symbol); REGISTER_FUNCTION(import_package); REGISTER_FUNCTION(find_package); // #################### // # Symbol Functions # // #################### REGISTER_FUNCTION(symbolp); REGISTER_FUNCTION(keywordp); REGISTER_FUNCTION(const_value_p); REGISTER_FUNCTION(const_func_p); REGISTER_FUNCTION(specialp); REGISTER_FUNCTION(make_symbol); REGISTER_FUNCTION(make_symbol_special); REGISTER_FUNCTION(symbol_package); REGISTER_FUNCTION(symbol_name); REGISTER_FUNCTION(symbol_function); REGISTER_FUNCTION(fset); REGISTER_FUNCTION(symbol_value); REGISTER_FUNCTION(set); REGISTER_FUNCTION(symbol_value_docstr); REGISTER_FUNCTION(set_symbol_value_docstr); REGISTER_FUNCTION(symbol_plist); REGISTER_FUNCTION(setplist); REGISTER_FUNCTION(exported_symbol_p); REGISTER_FUNCTION(intern_soft); REGISTER_FUNCTION(intern); REGISTER_FUNCTION(quote_symbol_name); REGISTER_FUNCTION(symbol_accessible_p); REGISTER_FUNCTION(quote_symbol_for_read); // ######################## // # Hash Table Functions # // ######################## REGISTER_FUNCTION(hash_table_p); REGISTER_FUNCTION(make_hash_table); REGISTER_FUNCTION(copy_hash_table); REGISTER_FUNCTION(hash_table_count); REGISTER_FUNCTION(maphash); REGISTER_FUNCTION(puthash); REGISTER_FUNCTION(gethash); REGISTER_FUNCTION(remhash); // ##################### // # Numeric Functions # // ##################### REGISTER_FUNCTION(integerp); REGISTER_FUNCTION(floatp); REGISTER_FUNCTION(num_eq); REGISTER_FUNCTION(num_gt); REGISTER_FUNCTION(add); REGISTER_FUNCTION(sub); REGISTER_FUNCTION(mul); REGISTER_FUNCTION(div); // #################### // # Vector Functions # // #################### REGISTER_FUNCTION(vectorp); REGISTER_FUNCTION(make_vector); REGISTER_FUNCTION(vector); REGISTER_FUNCTION(vector_length); REGISTER_FUNCTION(aref); REGISTER_FUNCTION(aset); REGISTER_FUNCTION(subvector); // #################### // # String Functions # // #################### REGISTER_FUNCTION(stringp); REGISTER_FUNCTION(string); REGISTER_FUNCTION(hash_string); REGISTER_FUNCTION(strings_equal); REGISTER_FUNCTION(string_to_vector); REGISTER_FUNCTION(quote_string); REGISTER_FUNCTION(concat); // #################### // # Record Functions # // #################### REGISTER_FUNCTION(recordp); REGISTER_FUNCTION(make_record); REGISTER_FUNCTION(record_function); REGISTER_FUNCTION(set_record_function); REGISTER_FUNCTION(record_length); REGISTER_FUNCTION(record_slot); REGISTER_FUNCTION(set_record_slot); // ################ // # IO Functions # // ################ REGISTER_FUNCTION(print); REGISTER_FUNCTION(println); // ######################## // # Lexenv and the Stack # // ######################## REGISTER_FUNCTION(backtrace); REGISTER_FUNCTION(throw); // ############### // # From read.c # // ############### register_reader_functions(); }