Initial commit
This commit is contained in:
		
							
								
								
									
										552
									
								
								src/lisp.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										552
									
								
								src/lisp.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,552 @@ | ||||
| #include "lisp.h" | ||||
|  | ||||
| #include <ctype.h> | ||||
| #include <stdarg.h> | ||||
| #include <stdio.h> | ||||
| #include <string.h> | ||||
|  | ||||
| struct _TypeNameEntry LISP_TYPE_NAMES[N_LISP_TYPES] = { | ||||
|     [TYPE_NULL] = {"null", sizeof("null") - 1}, | ||||
|     [TYPE_STRING] = {"string", sizeof("string") - 1}, | ||||
|     [TYPE_SYMBOL] = {"symbol", sizeof("symbol") - 1}, | ||||
|     [TYPE_PAIR] = {"pair", sizeof("pair") - 1}, | ||||
|     [TYPE_INTEGER] = {"integer", sizeof("integer") - 1}, | ||||
|     [TYPE_FLOAT] = {"float", sizeof("float") - 1}, | ||||
|     [TYPE_VECTOR] = {"vector", sizeof("vector") - 1}, | ||||
|     [TYPE_FUNCTION] = {"function", sizeof("function") - 1}, | ||||
|     [TYPE_HASHTABLE] = {"hashtable", sizeof("hashtable") - 1}, | ||||
| }; | ||||
|  | ||||
| LispVal _Qnil = { | ||||
|     .type = TYPE_NULL, | ||||
|     .ref_count = -1, | ||||
| }; | ||||
|  | ||||
| DEF_STATIC_STRING(_Qunbound_name, "unbound"); | ||||
| LispSymbol _Qunbound = { | ||||
|     .type = TYPE_SYMBOL, | ||||
|     .ref_count = -1, | ||||
|     .name = &_Qunbound_name, | ||||
|     .plist = Qnil, | ||||
|     .function = Qunbound, | ||||
|     .value = Qunbound, | ||||
| }; | ||||
|  | ||||
| DEF_STATIC_STRING(_Qt_name, "t"); | ||||
| LispSymbol _Qt = { | ||||
|     .type = TYPE_SYMBOL, | ||||
|     .ref_count = -1, | ||||
|     .name = &_Qt_name, | ||||
|     .plist = Qnil, | ||||
|     .function = Qunbound, | ||||
|     .value = Qunbound, | ||||
| }; | ||||
|  | ||||
| DEF_STATIC_SYMBOL(quote, "'"); | ||||
| DEF_STATIC_SYMBOL(backquote, "`"); | ||||
| DEF_STATIC_SYMBOL(comma, ","); | ||||
|  | ||||
| void _internal_lisp_delete_object(LispVal *val) { | ||||
|     switch (TYPEOF(val)) { | ||||
|     case TYPE_NULL: | ||||
|     case TYPE_INTEGER: | ||||
|     case TYPE_FLOAT: | ||||
|         lisp_free(val); | ||||
|         break; | ||||
|     case TYPE_STRING: { | ||||
|         LispString *str = (LispString *) val; | ||||
|         if (!str->is_static) { | ||||
|             lisp_free(str->data); | ||||
|         } | ||||
|         lisp_free(val); | ||||
|     } break; | ||||
|     case TYPE_SYMBOL: { | ||||
|         LispSymbol *sym = (LispSymbol *) val; | ||||
|         lisp_unref(sym->name); | ||||
|         lisp_unref(sym->plist); | ||||
|         lisp_unref(sym->function); | ||||
|         lisp_unref(sym->value); | ||||
|         lisp_free(val); | ||||
|     } break; | ||||
|     case TYPE_PAIR: | ||||
|         lisp_unref(((LispPair *) val)->head); | ||||
|         lisp_unref(((LispPair *) val)->tail); | ||||
|         lisp_free(val); | ||||
|         break; | ||||
|     case TYPE_VECTOR: { | ||||
|         LispVector *vec = (LispVector *) val; | ||||
|         for (size_t i = 0; i < vec->length; ++i) { | ||||
|             lisp_unref(vec->data[i]); | ||||
|         } | ||||
|         lisp_free(vec->data); | ||||
|         lisp_free(val); | ||||
|     } break; | ||||
|     case TYPE_FUNCTION: | ||||
|         // TODO handle | ||||
|         break; | ||||
|     case TYPE_HASHTABLE: { | ||||
|         LispHashtable *tbl = (LispHashtable *) val; | ||||
|         for (size_t i = 0; i < tbl->table_size; ++i) { | ||||
|             struct HashtableBucket *cur = tbl->data[i]; | ||||
|             while (cur) { | ||||
|                 lisp_unref(cur->key); | ||||
|                 lisp_unref(cur->value); | ||||
|                 struct HashtableBucket *next = cur->next; | ||||
|                 lisp_free(cur); | ||||
|                 cur = next; | ||||
|             } | ||||
|         } | ||||
|         lisp_free(tbl->data); | ||||
|         lisp_unref(tbl->eq_fn); | ||||
|         lisp_unref(tbl->hash_fn); | ||||
|         lisp_free(val); | ||||
|     } break; | ||||
|     default: | ||||
|         abort(); | ||||
|     }; | ||||
| } | ||||
|  | ||||
| void *lisp_malloc(size_t size) { | ||||
|     return lisp_realloc(NULL, size); | ||||
| } | ||||
|  | ||||
| void *lisp_realloc(void *old_ptr, size_t size) { | ||||
|     if (!size) { | ||||
|         return NULL; | ||||
|     } | ||||
|     void *new_ptr = realloc(old_ptr, size); | ||||
|     if (!new_ptr) { | ||||
|         abort(); | ||||
|     } | ||||
|     return new_ptr; | ||||
| } | ||||
|  | ||||
| char *lisp_strdup(const char *str) { | ||||
|     size_t len = strlen(str); | ||||
|     char *new_str = lisp_malloc(len + 1); | ||||
|     memcpy(new_str, str, len + 1); | ||||
|     return new_str; | ||||
| } | ||||
|  | ||||
| LispVal *make_lisp_string(const char *data, size_t length, bool take, | ||||
|                           bool is_static) { | ||||
|     LispString *self = lisp_malloc(sizeof(LispString)); | ||||
|     self->type = TYPE_STRING; | ||||
|     self->ref_count = 0; | ||||
|     if (take) { | ||||
|         self->data = (char *) data; | ||||
|     } else { | ||||
|         self->data = lisp_strdup(data); | ||||
|     } | ||||
|     self->length = length; | ||||
|     self->is_static = is_static; | ||||
|     return LISPVAL(self); | ||||
| } | ||||
|  | ||||
| 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, true, false); | ||||
|     va_end(args); | ||||
|     return obj; | ||||
| } | ||||
|  | ||||
| LispVal *make_lisp_symbol(LispVal *name) { | ||||
|     LispSymbol *self = lisp_malloc(sizeof(LispSymbol)); | ||||
|     self->type = TYPE_SYMBOL; | ||||
|     self->ref_count = 0; | ||||
|     self->name = (LispString *) lisp_ref(name); | ||||
|     self->plist = Qnil; | ||||
|     self->function = Qunbound; | ||||
|     self->value = Qunbound; | ||||
|     return LISPVAL(self); | ||||
| } | ||||
|  | ||||
| LispVal *make_lisp_pair(LispVal *head, LispVal *tail) { | ||||
|     LispPair *self = lisp_malloc(sizeof(LispPair)); | ||||
|     self->type = TYPE_PAIR; | ||||
|     self->ref_count = 0; | ||||
|     self->head = lisp_ref(head); | ||||
|     self->tail = lisp_ref(tail); | ||||
|     return LISPVAL(self); | ||||
| } | ||||
|  | ||||
| LispVal *make_lisp_integer(intmax_t value) { | ||||
|     LispInteger *self = lisp_malloc(sizeof(LispInteger)); | ||||
|     self->type = TYPE_INTEGER; | ||||
|     self->ref_count = 0; | ||||
|     self->value = value; | ||||
|     return LISPVAL(self); | ||||
| } | ||||
|  | ||||
| LispVal *make_lisp_float(long double value) { | ||||
|     LispFloat *self = lisp_malloc(sizeof(LispFloat)); | ||||
|     self->type = TYPE_FLOAT; | ||||
|     self->ref_count = 0; | ||||
|     self->value = value; | ||||
|     return LISPVAL(self); | ||||
| } | ||||
|  | ||||
| LispVal *make_lisp_vector(LispVal **data, size_t length) { | ||||
|     LispVector *self = lisp_malloc(sizeof(LispVector)); | ||||
|     self->type = TYPE_VECTOR; | ||||
|     self->ref_count = 0; | ||||
|     self->data = data; | ||||
|     self->length = length; | ||||
|     return LISPVAL(self); | ||||
| } | ||||
|  | ||||
| LispVal *make_lisp_hashtable(LispVal *eq_fn, LispVal *hash_fn) { | ||||
|     LispHashtable *self = lisp_malloc(sizeof(LispHashtable)); | ||||
|     self->type = TYPE_HASHTABLE; | ||||
|     self->ref_count = 0; | ||||
|     self->table_size = LISP_HASHTABLE_INITIAL_SIZE; | ||||
|     self->data = | ||||
|         lisp_malloc(sizeof(struct HashtableBucket *) * self->table_size); | ||||
|     memset(self->data, 0, sizeof(struct HashtableBucket *) * self->table_size); | ||||
|     self->count = 0; | ||||
|     self->eq_fn = eq_fn; | ||||
|     self->hash_fn = hash_fn; | ||||
|     return LISPVAL(self); | ||||
| } | ||||
|  | ||||
| DEFUN(type_of, "type-of", (LispVal * obj)) { | ||||
|     if (obj->type < 0 || obj->type >= N_LISP_TYPES) { | ||||
|         return Qnil; | ||||
|     } | ||||
|     LispVal *name = | ||||
|         make_lisp_string((char *) LISP_TYPE_NAMES[obj->type].name, | ||||
|                          LISP_TYPE_NAMES[obj->type].len, true, true); | ||||
|     lisp_ref(name); | ||||
|     LispVal *sym = Fintern(name); | ||||
|     UNREF_INPLACE(name); | ||||
|     return sym; | ||||
| } | ||||
|  | ||||
| DEFUN(pair, "pair", (LispVal * head, LispVal *tail)) { | ||||
|     return make_lisp_pair(head, tail); | ||||
| } | ||||
|  | ||||
| DEFUN(hash_string, "hash-string", (LispVal * obj)) { | ||||
|     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)) { | ||||
|     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); | ||||
| } | ||||
|  | ||||
| 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; | ||||
| } | ||||
|  | ||||
| DEFUN(id, "id", (LispVal * obj)) { | ||||
|     return make_lisp_integer((int64_t) obj); | ||||
| } | ||||
|  | ||||
| DEFUN(eq, "eq", (LispVal * obj1, LispVal *obj2)) { | ||||
|     return LISP_BOOL(obj1 == obj2); | ||||
| } | ||||
|  | ||||
| static bool hash_table_eq(LispHashtable *self, LispVal *v1, LispVal *v2) { | ||||
|     if (NILP(self->eq_fn)) { | ||||
|         return v1 == v2; | ||||
|     } else if (self->eq_fn == Qstrings_equal) { | ||||
|         return !NILP(Fstrings_equal(v1, v2)); | ||||
|     } else { | ||||
|         // TODO call the function | ||||
|         return false; | ||||
|     } | ||||
| } | ||||
|  | ||||
| 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 lookups faster | ||||
|         LispVal *hash_obj = Fhash_string(key); | ||||
|         uint64_t hash = ((LispInteger *) hash_obj)->value; | ||||
|         UNREF_INPLACE(hash_obj); | ||||
|         return hash; | ||||
|     } else { | ||||
|         // TODO call the hash function | ||||
|         return 0; | ||||
|     } | ||||
| } | ||||
|  | ||||
| static struct HashtableBucket * | ||||
| find_hash_table_bucket(LispHashtable *self, LispVal *key, uint64_t hash) { | ||||
|     struct HashtableBucket *cur = self->data[hash % self->table_size]; | ||||
|     while (cur) { | ||||
|         if (hash_table_eq(self, key, cur->key)) { | ||||
|             return cur; | ||||
|         } | ||||
|         cur = cur->next; | ||||
|     } | ||||
|     return NULL; | ||||
| } | ||||
|  | ||||
| static void hash_table_rehash(LispHashtable *self, size_t new_size) { | ||||
|     struct HashtableBucket **new_data = | ||||
|         lisp_malloc(sizeof(struct HashtableBucket *) * new_size); | ||||
|     memset(new_data, 0, sizeof(struct HashtableBucket *) * new_size); | ||||
|     for (size_t i = 0; i < self->table_size; ++i) { | ||||
|         struct HashtableBucket *cur = self->data[i]; | ||||
|         while (cur) { | ||||
|             struct HashtableBucket *next = cur->next; | ||||
|             cur->next = new_data[cur->hash % new_size]; | ||||
|             new_data[cur->hash % new_size] = cur; | ||||
|             cur = next; | ||||
|         } | ||||
|     } | ||||
|     free(self->data); | ||||
|     self->data = new_data; | ||||
|     self->table_size = new_size; | ||||
| } | ||||
|  | ||||
| DEFUN(puthash, "puthash", (LispVal * table, LispVal *key, LispVal *value)) { | ||||
|     CHECK_TYPE(TYPE_HASHTABLE, table); | ||||
|     LispHashtable *self = (LispHashtable *) table; | ||||
|     uint64_t hash = hash_table_hash(self, key); | ||||
|     struct HashtableBucket *cur_bucket = | ||||
|         find_hash_table_bucket(self, key, hash); | ||||
|     if (cur_bucket) { | ||||
|         UNREF_INPLACE(cur_bucket->value); | ||||
|         cur_bucket->value = lisp_ref(value); | ||||
|     } else { | ||||
|         cur_bucket = lisp_malloc(sizeof(struct HashtableBucket)); | ||||
|         cur_bucket->next = self->data[hash % self->table_size]; | ||||
|         cur_bucket->hash = hash; | ||||
|         cur_bucket->key = lisp_ref(key); | ||||
|         cur_bucket->value = lisp_ref(value); | ||||
|         self->data[hash % self->table_size] = cur_bucket; | ||||
|         ++self->count; | ||||
|         if ((double) self->count / self->table_size | ||||
|             >= LISP_HASHTABLE_GROWTH_THRESHOLD) { | ||||
|             hash_table_rehash(self, | ||||
|                               LISP_HASHTABLE_GROWTH_FACTOR * self->table_size); | ||||
|         } | ||||
|     } | ||||
|     return table; | ||||
| } | ||||
|  | ||||
| DEFUN(gethash, "gethash", (LispVal * table, LispVal *key, LispVal *def)) { | ||||
|     CHECK_TYPE(TYPE_HASHTABLE, table); | ||||
|     LispHashtable *self = (LispHashtable *) table; | ||||
|     uint64_t hash = hash_table_hash(self, key); | ||||
|     struct HashtableBucket *cur_bucket = | ||||
|         find_hash_table_bucket(self, key, hash); | ||||
|     if (cur_bucket) { | ||||
|         return cur_bucket->value; | ||||
|     } | ||||
|     return def; | ||||
| } | ||||
|  | ||||
| DEFUN(remhash, "remhash", (LispVal * table, LispVal *key)) { | ||||
|     CHECK_TYPE(TYPE_HASHTABLE, table); | ||||
|     LispHashtable *self = (LispHashtable *) table; | ||||
|     uint64_t hash = hash_table_hash(self, key); | ||||
|     struct HashtableBucket *cur_bucket = self->data[hash % self->table_size]; | ||||
|     if (cur_bucket && hash_table_eq(self, cur_bucket->key, key)) { | ||||
|         self->data[hash % self->table_size] = cur_bucket->next; | ||||
|         UNREF_INPLACE(cur_bucket->key); | ||||
|         UNREF_INPLACE(cur_bucket->value); | ||||
|         free(cur_bucket); | ||||
|         --self->count; | ||||
|     } else { | ||||
|         struct HashtableBucket *prev_bucket = cur_bucket; | ||||
|         cur_bucket = cur_bucket->next; | ||||
|         while (cur_bucket) { | ||||
|             if (hash_table_eq(self, cur_bucket->key, key)) { | ||||
|                 prev_bucket->next = cur_bucket->next; | ||||
|                 UNREF_INPLACE(cur_bucket->key); | ||||
|                 UNREF_INPLACE(cur_bucket->value); | ||||
|                 free(cur_bucket); | ||||
|                 --self->count; | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|     if ((double) self->count / self->table_size | ||||
|             <= LISP_HASHTABLE_SHRINK_THRESHOLD | ||||
|         && self->table_size > LISP_HASHTABLE_INITIAL_SIZE) { | ||||
|         hash_table_rehash(self, | ||||
|                           self->table_size / LISP_HASHTABLE_GROWTH_FACTOR); | ||||
|     } | ||||
|     return table; | ||||
| } | ||||
|  | ||||
| DEFUN(hash_table_count, "hash-table-count", (LispVal * table)) { | ||||
|     CHECK_TYPE(TYPE_HASHTABLE, table); | ||||
|     return make_lisp_integer(((LispHashtable *) table)->count); | ||||
| } | ||||
|  | ||||
| DEFUN(intern, "intern", (LispVal * name)) { | ||||
|     CHECK_TYPE(TYPE_STRING, name); | ||||
|     LispVal *cur = Fgethash(Vobarray, name, Qunbound); | ||||
|     if (cur != Qunbound) { | ||||
|         return cur; | ||||
|     } | ||||
|     LispVal *sym = make_lisp_symbol(name); | ||||
|     Fputhash(Vobarray, name, sym); | ||||
|     return sym; | ||||
| } | ||||
|  | ||||
| LispVal *intern(const char *name, size_t length, bool take) { | ||||
|     LispVal *name_obj = make_lisp_string((char *) name, length, take, false); | ||||
|     lisp_ref(name_obj); | ||||
|     LispVal *sym = Fintern(name_obj); | ||||
|     UNREF_INPLACE(name_obj); | ||||
|     return sym; | ||||
| } | ||||
|  | ||||
| DEFUN(sethead, "sethead", (LispVal * pair, LispVal *head)) { | ||||
|     CHECK_TYPE(TYPE_PAIR, pair); | ||||
|     UNREF_INPLACE(((LispPair *) pair)->head); | ||||
|     ((LispPair *) pair)->head = lisp_ref(head); | ||||
|     return Qnil; | ||||
| } | ||||
|  | ||||
| DEFUN(settail, "settail", (LispVal * pair, LispVal *tail)) { | ||||
|     CHECK_TYPE(TYPE_PAIR, pair); | ||||
|     UNREF_INPLACE(((LispPair *) pair)->tail); | ||||
|     ((LispPair *) pair)->tail = lisp_ref(tail); | ||||
|     return Qnil; | ||||
| } | ||||
|  | ||||
| DEFUN(throw, "throw", (LispVal * signal, LispVal *rest)) { | ||||
|     if (!SYMBOLP(signal)) { | ||||
|         printf("Attempt to throw non-symbol value!\n"); | ||||
|     } else { | ||||
|         LispSymbol *sym = (LispSymbol *) signal; | ||||
|         printf("Throw %*s! Data: ", (int) sym->name->length, sym->name->data); | ||||
|         debug_dump(stdout, rest, true); | ||||
|     } | ||||
|     return Qnil; | ||||
| } | ||||
|  | ||||
| DEF_STATIC_SYMBOL(type_error, "type-error"); | ||||
| DEF_STATIC_SYMBOL(read_error, "read-error"); | ||||
|  | ||||
| LispVal *Vobarray = Qnil; | ||||
|  | ||||
| void lisp_init() { | ||||
|     Vobarray = lisp_ref(make_lisp_hashtable(Qstrings_equal, Qhash_string)); | ||||
| } | ||||
|  | ||||
| void lisp_shutdown() { | ||||
|     UNREF_INPLACE(Vobarray); | ||||
| } | ||||
|  | ||||
| static void debug_dump_real(FILE *stream, void *obj, bool first) { | ||||
|     switch (TYPEOF(obj)) { | ||||
|     case TYPE_NULL: | ||||
|         fprintf(stream, "nil"); | ||||
|         break; | ||||
|     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; | ||||
|         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", ((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: | ||||
|         if (((LispFunction *) obj)->builtin) { | ||||
|             fprintf(stream, "<builtin at %#jx>", (uintmax_t) obj); | ||||
|         } else { | ||||
|             fprintf(stream, "<function at %#jx>", (uintmax_t) obj); | ||||
|         } | ||||
|         break; | ||||
|     case TYPE_HASHTABLE: { | ||||
|         LispHashtable *tbl = (LispHashtable *) obj; | ||||
|         fprintf(stream, "<hashtable size=%zu count=%zu at %#jx>", | ||||
|                 tbl->table_size, tbl->count, (uintmax_t) obj); | ||||
|     } break; | ||||
|     default: | ||||
|         fprintf(stream, "<object type=%ju at %#jx>", | ||||
|                 (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); | ||||
|     HASHTABLE_FOREACH(key, val, table, { | ||||
|         fprintf(stream, "- "); | ||||
|         debug_dump(stream, key, false); | ||||
|         fprintf(stream, " = "); | ||||
|         debug_dump(stream, val, true); | ||||
|     }); | ||||
| } | ||||
							
								
								
									
										326
									
								
								src/lisp.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										326
									
								
								src/lisp.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,326 @@ | ||||
| #ifndef INCLUDED_LISP_H | ||||
| #define INCLUDED_LISP_H | ||||
|  | ||||
| #include <stdarg.h> | ||||
| #include <stdbool.h> | ||||
| #include <stddef.h> | ||||
| #include <stdint.h> | ||||
| #include <stdio.h> | ||||
| #include <stdlib.h> | ||||
|  | ||||
| #if __has_attribute(format) | ||||
| #    define PRINTF_FORMAT(first, second) \ | ||||
|         __attribute__((format(printf, first, second))) | ||||
| #else | ||||
| #    define PRINTF_FORMAT(first, second) | ||||
| #endif | ||||
|  | ||||
| typedef enum { | ||||
|     TYPE_NULL = 0, | ||||
|     TYPE_STRING, | ||||
|     TYPE_SYMBOL, | ||||
|     TYPE_PAIR, | ||||
|     TYPE_INTEGER, | ||||
|     TYPE_FLOAT, | ||||
|     TYPE_VECTOR, | ||||
|     TYPE_FUNCTION, | ||||
|     TYPE_HASHTABLE, | ||||
|     N_LISP_TYPES, | ||||
| } LispType; | ||||
|  | ||||
| struct _TypeNameEntry { | ||||
|     const char *name; | ||||
|     size_t len; | ||||
| }; | ||||
| extern struct _TypeNameEntry LISP_TYPE_NAMES[N_LISP_TYPES]; | ||||
| #define OBJ_TYPE_NAME(obj) (LISP_TYPE_NAMES[LISPVAL(obj)->type].name) | ||||
|  | ||||
| #define LISP_OBJECT_HEADER \ | ||||
|     LispType type;         \ | ||||
|     ptrdiff_t ref_count; | ||||
|  | ||||
| typedef struct { | ||||
|     LISP_OBJECT_HEADER; | ||||
| } LispVal; | ||||
| #define LISPVAL(obj) ((LispVal *) (obj)) | ||||
| #define STATICP(v)   (LISPVAL(v)->ref_count < 0) | ||||
| #define TYPEOF(v)    (LISPVAL(v)->type) | ||||
|  | ||||
| typedef struct { | ||||
|     LISP_OBJECT_HEADER; | ||||
|  | ||||
|     char *data; | ||||
|     size_t length; | ||||
|     bool is_static; | ||||
| } LispString; | ||||
|  | ||||
| typedef struct { | ||||
|     LISP_OBJECT_HEADER; | ||||
|  | ||||
|     LispString *name; | ||||
|     LispVal *plist; | ||||
|     LispVal *function; | ||||
|     LispVal *value; | ||||
| } LispSymbol; | ||||
|  | ||||
| typedef struct { | ||||
|     LISP_OBJECT_HEADER; | ||||
|  | ||||
|     LispVal *head; | ||||
|     LispVal *tail; | ||||
| } LispPair; | ||||
|  | ||||
| typedef struct { | ||||
|     LISP_OBJECT_HEADER; | ||||
|  | ||||
|     intmax_t value; | ||||
| } LispInteger; | ||||
|  | ||||
| typedef struct { | ||||
|     LISP_OBJECT_HEADER; | ||||
|  | ||||
|     long double value; | ||||
| } LispFloat; | ||||
|  | ||||
| typedef struct { | ||||
|     LISP_OBJECT_HEADER; | ||||
|  | ||||
|     LispVal **data; | ||||
|     size_t length; | ||||
| } LispVector; | ||||
|  | ||||
| typedef LispVal *(*lisp_builtin_t)(); | ||||
|  | ||||
| typedef struct { | ||||
|     LISP_OBJECT_HEADER; | ||||
|  | ||||
|     LispVal *doc; | ||||
|     LispVal *args; | ||||
|     bool is_builtin; | ||||
|     union { | ||||
|         LispVal *body; | ||||
|         lisp_builtin_t builtin; | ||||
|     }; | ||||
| } LispFunction; | ||||
|  | ||||
| struct HashtableBucket { | ||||
|     struct HashtableBucket *next; | ||||
|     uint64_t hash; | ||||
|     LispVal *key; | ||||
|     LispVal *value; | ||||
| }; | ||||
|  | ||||
| #define LISP_HASHTABLE_INITIAL_SIZE     32 | ||||
| #define LISP_HASHTABLE_GROWTH_FACTOR    2 | ||||
| #define LISP_HASHTABLE_GROWTH_THRESHOLD 0.75f | ||||
| #define LISP_HASHTABLE_SHRINK_THRESHOLD 0.25f | ||||
|  | ||||
| typedef struct { | ||||
|     LISP_OBJECT_HEADER; | ||||
|  | ||||
|     struct HashtableBucket **data; | ||||
|     size_t table_size; | ||||
|     size_t count; | ||||
|     LispVal *eq_fn; | ||||
|     LispVal *hash_fn; | ||||
| } LispHashtable; | ||||
|  | ||||
| #define NILP(v)       (TYPEOF(v) == TYPE_NULL) | ||||
| #define STRINGP(v)    (TYPEOF(v) == TYPE_STRING) | ||||
| #define SYMBOLP(v)    (TYPEOF(v) == TYPE_SYMBOL) | ||||
| #define PAIRP(v)      (TYPEOF(v) == TYPE_PAIR) | ||||
| #define INTEGERP(v)   (TYPEOF(v) == TYPE_INTEGER) | ||||
| #define FLOATP(v)     (TYPEOF(v) == TYPE_FLOAT) | ||||
| #define VECTORP(v)    (TYPEOF(v) == TYPE_VECTOR) | ||||
| #define FUNCTIONP(v)  (TYPEOF(v) == TYPE_FUNCTION) | ||||
| #define HASHTABLEP(v) (TYPEOF(v) == TYPE_HASHTABLE) | ||||
|  | ||||
| #define ATOM(v) (TYPEOF(v) != TYPE_PAIR) | ||||
|  | ||||
| inline static bool LISTP(LispVal *v) { | ||||
|     return NILP(v) || PAIRP(v); | ||||
| } | ||||
|  | ||||
| inline static bool NUMBERP(LispVal *v) { | ||||
|     return INTEGERP(v) || FLOATP(v); | ||||
| } | ||||
|  | ||||
| extern LispVal _Qnil; | ||||
| extern LispSymbol _Qunbound; | ||||
| extern LispSymbol _Qt; | ||||
|  | ||||
| #define Qnil     (&_Qnil) | ||||
| #define Qunbound (LISPVAL(&_Qunbound)) | ||||
| #define Qt       (LISPVAL(&_Qt)) | ||||
|  | ||||
| extern LispVal *Qquote; | ||||
| extern LispVal *Qbackquote; | ||||
| extern LispVal *Qcomma; | ||||
|  | ||||
| #define LISP_BOOL(v) ((v) ? Qt : Qnil) | ||||
|  | ||||
| #define DEF_STATIC_STRING(name, value) \ | ||||
|     static LispString name = {         \ | ||||
|         .type = TYPE_STRING,           \ | ||||
|         .ref_count = -1,               \ | ||||
|         .data = value,                 \ | ||||
|         .length = sizeof(value) - 1,   \ | ||||
|         .is_static = true,             \ | ||||
|     }; | ||||
| #define DEF_STATIC_SYMBOL(c_name, lisp_name)         \ | ||||
|     DEF_STATIC_STRING(_Q##c_name##_name, lisp_name); \ | ||||
|     static LispSymbol _Q##c_name = {                 \ | ||||
|         .type = TYPE_SYMBOL,                         \ | ||||
|         .ref_count = -1,                             \ | ||||
|         .name = &_Q##c_name##_name,                  \ | ||||
|         .plist = Qnil,                               \ | ||||
|         .function = Qunbound,                        \ | ||||
|         .value = Qunbound,                           \ | ||||
|     };                                               \ | ||||
|     LispVal *Q##c_name = LISPVAL(&_Q##c_name); | ||||
|  | ||||
| void *lisp_malloc(size_t size); | ||||
| void *lisp_realloc(void *old_ptr, size_t size); | ||||
| #define lisp_free free | ||||
| char *lisp_strdup(const char *str); | ||||
|  | ||||
| inline static void *lisp_ref(void *val) { | ||||
|     if (!STATICP(val)) { | ||||
|         ++((LispVal *) val)->ref_count; | ||||
|     } | ||||
|     return val; | ||||
| } | ||||
|  | ||||
| inline static void *lisp_float_ref(void *val) { | ||||
|     if (LISPVAL(val)->ref_count > 0) { | ||||
|         --LISPVAL(val)->ref_count; | ||||
|     } | ||||
|     return val; | ||||
| } | ||||
|  | ||||
| void _internal_lisp_delete_object(LispVal *val); | ||||
| inline static void *lisp_unref(void *val) { | ||||
|     if (STATICP(val)) { | ||||
|         return val; | ||||
|     } else if (LISPVAL(val)->ref_count > 1) { | ||||
|         --LISPVAL(val)->ref_count; | ||||
|         return val; | ||||
|     } else { | ||||
|         _internal_lisp_delete_object(val); | ||||
|         return Qnil; | ||||
|     } | ||||
| } | ||||
| #define UNREF_INPLACE(variable)          \ | ||||
|     {                                    \ | ||||
|         variable = lisp_unref(variable); \ | ||||
|     } | ||||
|  | ||||
| LispVal *make_lisp_string(const char *data, size_t length, bool take, | ||||
|                           bool is_static); | ||||
| #define STATIC_STRING(s) (make_lisp_string((s), sizeof(s) - 1, true, true)) | ||||
| LispVal *sprintf_lisp(const char *format, ...) PRINTF_FORMAT(1, 2); | ||||
| LispVal *make_lisp_symbol(LispVal *name); | ||||
| LispVal *make_lisp_pair(LispVal *head, LispVal *tail); | ||||
| LispVal *make_lisp_integer(intmax_t value); | ||||
| LispVal *make_lisp_float(long double value); | ||||
| LispVal *make_lisp_vector(LispVal **data, size_t length); | ||||
| // TODO make_lisp_function | ||||
| LispVal *make_lisp_hashtable(LispVal *eq_fn, LispVal *hash_fn); | ||||
|  | ||||
| #define DECLARE_FUNCTION(c_name, args) \ | ||||
|     extern LispVal *Q##c_name;         \ | ||||
|     LispVal *F##c_name args; | ||||
|  | ||||
| // The args and doc fields are filled when the function is registered | ||||
| #define DEFUN(c_name, lisp_name, c_args)             \ | ||||
|     DEF_STATIC_STRING(_Q##c_name##_name, lisp_name); \ | ||||
|     static LispFunction _Q##c_name##_function = {    \ | ||||
|         .type = TYPE_FUNCTION,                       \ | ||||
|         .ref_count = -1,                             \ | ||||
|         .doc = Qnil,                                 \ | ||||
|         .args = Qnil,                                \ | ||||
|         .is_builtin = true,                          \ | ||||
|         .builtin = &F##c_name,                       \ | ||||
|     };                                               \ | ||||
|     static LispSymbol _Q##c_name = {                 \ | ||||
|         .type = TYPE_SYMBOL,                         \ | ||||
|         .ref_count = -1,                             \ | ||||
|         .name = &_Q##c_name##_name,                  \ | ||||
|         .plist = Qnil,                               \ | ||||
|         .function = LISPVAL(&_Q##c_name##_function), \ | ||||
|     };                                               \ | ||||
|     LispVal *Q##c_name = (LispVal *) &_Q##c_name;    \ | ||||
|     LispVal *F##c_name c_args | ||||
|  | ||||
| DECLARE_FUNCTION(type_of, (LispVal * obj)); | ||||
| DECLARE_FUNCTION(pair, (LispVal * head, LispVal *tail)); | ||||
| DECLARE_FUNCTION(hash_string, (LispVal * obj)); | ||||
| DECLARE_FUNCTION(strings_equal, (LispVal * obj1, LispVal *obj2)); | ||||
| bool strings_equal_nocase(const char *s1, const char *s2, size_t n); | ||||
| DECLARE_FUNCTION(id, (LispVal * obj)); | ||||
| DECLARE_FUNCTION(eq, (LispVal * obj1, LispVal *obj2)); | ||||
| DECLARE_FUNCTION(puthash, (LispVal * table, LispVal *key, LispVal *value)); | ||||
| DECLARE_FUNCTION(gethash, (LispVal * table, LispVal *key, LispVal *def)); | ||||
| DECLARE_FUNCTION(remhash, (LispVal * table, LispVal *key)); | ||||
| DECLARE_FUNCTION(hash_table_count, (LispVal * table)); | ||||
| #define HASHTABLE_FOREACH(key_var, val_var, table, body)                    \ | ||||
|     {                                                                       \ | ||||
|         LispHashtable *__hashtable_foreach_table = (LispHashtable *) table; \ | ||||
|         for (size_t __hashtable_foreach_i = 0;                              \ | ||||
|              __hashtable_foreach_i < __hashtable_foreach_table->count;      \ | ||||
|              ++__hashtable_foreach_i) {                                     \ | ||||
|             struct HashtableBucket *__hashtable_foreach_cur =               \ | ||||
|                 __hashtable_foreach_table->data[__hashtable_foreach_i];     \ | ||||
|             while (__hashtable_foreach_cur) {                               \ | ||||
|                 LispVal *key_var = __hashtable_foreach_cur->key;            \ | ||||
|                 LispVal *val_var = __hashtable_foreach_cur->value;          \ | ||||
|                 {body};                                                     \ | ||||
|                 __hashtable_foreach_cur = __hashtable_foreach_cur->next;    \ | ||||
|             }                                                               \ | ||||
|         }                                                                   \ | ||||
|     } | ||||
| DECLARE_FUNCTION(intern, (LispVal * name)); | ||||
| LispVal *intern(const char *name, size_t length, bool take); | ||||
| #define INTERN_STATIC(name) (Fintern(STATIC_STRING(name))) | ||||
|  | ||||
| DECLARE_FUNCTION(sethead, (LispVal * pair, LispVal *head)); | ||||
| DECLARE_FUNCTION(settail, (LispVal * pair, LispVal *tail)); | ||||
| static inline LispVal *make_list(int len, ...) { | ||||
|     LispVal *list = Qnil; | ||||
|     LispVal *end; | ||||
|     va_list args; | ||||
|     va_start(args, len); | ||||
|     while (len--) { | ||||
|         LispVal *elt = va_arg(args, LispVal *); | ||||
|         if (NILP(list)) { | ||||
|             list = Fpair(elt, Qnil); | ||||
|             end = list; | ||||
|         } else { | ||||
|             LispVal *new_end = Fpair(elt, Qnil); | ||||
|             Fsettail(end, new_end); | ||||
|             end = new_end; | ||||
|         } | ||||
|     } | ||||
|     va_end(args); | ||||
|     return list; | ||||
| } | ||||
|  | ||||
| DECLARE_FUNCTION(throw, (LispVal * signal, LispVal *rest)); | ||||
|  | ||||
| extern LispVal *Qtype_error; | ||||
| extern LispVal *Qread_error; | ||||
|  | ||||
| #define CHECK_TYPE(type, val)      \ | ||||
|     if (TYPEOF(val) != type) {     \ | ||||
|         Fthrow(Qtype_error, Qnil); \ | ||||
|         return Qnil;               \ | ||||
|     } | ||||
|  | ||||
| extern LispVal *Vobarray; | ||||
|  | ||||
| void lisp_init(void); | ||||
| void lisp_shutdown(void); | ||||
| void debug_dump(FILE *stream, void *obj, bool newline); | ||||
| void debug_print_hashtable(FILE *stream, LispVal *table); | ||||
|  | ||||
| #endif | ||||
							
								
								
									
										18
									
								
								src/main.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								src/main.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,18 @@ | ||||
| #include "lisp.h" | ||||
| #include "read.h" | ||||
|  | ||||
| #include <string.h> | ||||
|  | ||||
| int main(int argc, const char **argv) { | ||||
|     lisp_init(); | ||||
|     char buffer[] = "1"; | ||||
|     LispVal *tv; | ||||
|     size_t count = read_from_buffer(buffer, sizeof(buffer) - 1, &tv); | ||||
|     lisp_ref(tv); | ||||
|     printf("Read %zu chars\n", count); | ||||
|     printf("Type: %s\n", OBJ_TYPE_NAME(tv)); | ||||
|     debug_dump(stdout, tv, true); | ||||
|     UNREF_INPLACE(tv); | ||||
|     lisp_shutdown(); | ||||
|     return 0; | ||||
| } | ||||
							
								
								
									
										404
									
								
								src/read.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										404
									
								
								src/read.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,404 @@ | ||||
| #include "read.h" | ||||
|  | ||||
| #include <ctype.h> | ||||
| #include <inttypes.h> | ||||
| #include <limits.h> | ||||
| #include <string.h> | ||||
|  | ||||
| struct ReadState { | ||||
|     const char *head; | ||||
|     size_t left; | ||||
|     size_t off; | ||||
|     size_t line; | ||||
|     size_t col; | ||||
|  | ||||
|     size_t backquote_level; | ||||
| }; | ||||
|  | ||||
| #define EOS -1 | ||||
|  | ||||
| static int popc(struct ReadState *state) { | ||||
|     if (!state->left) { | ||||
|         return EOS; | ||||
|     } | ||||
|     ++state->off; | ||||
|     --state->left; | ||||
|     int c = *(state->head++); | ||||
|     if (c == '\n') { | ||||
|         ++state->line; | ||||
|         state->off = 0; | ||||
|     } else { | ||||
|         ++state->col; | ||||
|     } | ||||
|     return c; | ||||
| } | ||||
|  | ||||
| static int peekc(struct ReadState *state) { | ||||
|     if (!state->left) { | ||||
|         return EOS; | ||||
|     } | ||||
|     return *state->head; | ||||
| } | ||||
|  | ||||
| static inline void _internal_read_error(struct ReadState *state, size_t len, | ||||
|                                         LispVal *desc) { | ||||
|     // TODO format better | ||||
|     LispVal *args = make_list( | ||||
|         4, make_lisp_integer(state->line), make_lisp_integer(state->col), | ||||
|         make_lisp_string(state->head, len, false, false), desc); | ||||
|     lisp_ref(args); | ||||
|     Fthrow(Qread_error, args); | ||||
|     UNREF_INPLACE(args); | ||||
| } | ||||
| #define READ_ERROR(state, len, ...) \ | ||||
|     _internal_read_error(state, len, sprintf_lisp(__VA_ARGS__)) | ||||
| #define EOF_ERROR(state) READ_ERROR(state, 1, "unexpected end of file") | ||||
|  | ||||
| #define SKIP_WHILE(cond, state) \ | ||||
|     while (cond) {              \ | ||||
|         popc(state);            \ | ||||
|     } | ||||
| #define SKIP_WHITESPACE(state) SKIP_WHILE(isblank(peekc(state)), state) | ||||
|  | ||||
| static bool is_symbol_end(int c) { | ||||
|     return c == EOS || isspace(c) || c == '(' || c == ')' || c == '[' | ||||
|            || c == ']'; | ||||
| } | ||||
|  | ||||
| static LispVal *read_internal(struct ReadState *state); | ||||
|  | ||||
| static LispVal *read_list(struct ReadState *state) { | ||||
|     popc(state); // open ( | ||||
|     LispVal *list = Qnil; | ||||
|     LispVal *end = list; | ||||
|     SKIP_WHITESPACE(state); | ||||
|     int c; | ||||
|     while ((c = peekc(state)) != ')') { | ||||
|         if (c == EOS) { | ||||
|             EOF_ERROR(state); | ||||
|             UNREF_INPLACE(list); | ||||
|             return Qnil; | ||||
|         } | ||||
|         LispVal *elt = read_internal(state); | ||||
|         if (NILP(list)) { | ||||
|             list = Fpair(elt, Qnil); | ||||
|             end = list; | ||||
|         } else { | ||||
|             LispVal *new_end = Fpair(elt, Qnil); | ||||
|             Fsettail(end, new_end); | ||||
|             end = new_end; | ||||
|         } | ||||
|         SKIP_WHITESPACE(state); | ||||
|     } | ||||
|     popc(state); // close ) | ||||
|     return list; | ||||
| } | ||||
|  | ||||
| static LispVal *read_vector(struct ReadState *state) { | ||||
|     popc(state); // open [ | ||||
|     LispVal **values = NULL; | ||||
|     size_t values_len = 0; | ||||
|     SKIP_WHITESPACE(state); | ||||
|     int c; | ||||
|     while ((c = peekc(state)) != ']') { | ||||
|         if (c == EOS) { | ||||
|             EOF_ERROR(state); | ||||
|             for (size_t i = 0; i < values_len; ++i) { | ||||
|                 lisp_unref(values[i]); | ||||
|             } | ||||
|             lisp_free(values); | ||||
|             return Qnil; | ||||
|         } | ||||
|         LispVal *elt = lisp_ref(read_internal(state)); | ||||
|         values = lisp_realloc(values, sizeof(LispVal *) * ++values_len); | ||||
|         values[values_len - 1] = elt; | ||||
|         SKIP_WHITESPACE(state); | ||||
|     } | ||||
|     popc(state); // close ] | ||||
|     return make_lisp_vector(values, values_len); | ||||
| } | ||||
|  | ||||
| static LispVal *read_string(struct ReadState *state) { | ||||
|     popc(state); // open " | ||||
|     bool backslash = false; | ||||
|     int c; | ||||
|     char *str = lisp_malloc(1); | ||||
|     size_t str_len = 0; | ||||
|     while (backslash || (c = peekc(state)) != '"') { | ||||
|         if (c == EOS) { | ||||
|             lisp_free(str); | ||||
|             EOF_ERROR(state); | ||||
|             return Qnil; | ||||
|         } | ||||
|         popc(state); | ||||
|         if (!backslash && c == '\\') { | ||||
|             backslash = true; | ||||
|         } else if (backslash && c == '\n') { | ||||
|             backslash = false; | ||||
|         } else { | ||||
|             str = lisp_realloc(str, ++str_len + 1); | ||||
|             int to_add = c; | ||||
|             if (backslash) { | ||||
|                 switch (c) { | ||||
|                 case 'n': | ||||
|                     to_add = '\n'; | ||||
|                     break; | ||||
|                 case 't': | ||||
|                     to_add = '\t'; | ||||
|                     break; | ||||
|                 case 'r': | ||||
|                     to_add = '\r'; | ||||
|                     break; | ||||
|                 case '0': | ||||
|                     to_add = '\0'; | ||||
|                     break; | ||||
|                 case '"': | ||||
|                     to_add = '"'; | ||||
|                     break; | ||||
|                 default: | ||||
|                     // TODO make this point at the correct thing | ||||
|                     READ_ERROR(state, 1, "unknown escape sequence"); | ||||
|                     lisp_free(str); | ||||
|                     return Qnil; | ||||
|                 } | ||||
|             } | ||||
|             backslash = false; | ||||
|             str[str_len - 1] = to_add; | ||||
|         } | ||||
|     } | ||||
|     str[str_len] = '\n'; | ||||
|     popc(state); // close " | ||||
|     return make_lisp_string(str, str_len, true, false); | ||||
| } | ||||
|  | ||||
| static LispVal *read_character(struct ReadState *state) { | ||||
|     static struct { | ||||
|         const char *name; | ||||
|         size_t len; | ||||
|         char value; | ||||
|     } LOOKUP_TABLE[] = { | ||||
|         {"null", 4, '\0'}, {"\\0", 2, '\0'}, {"newline", 7, '\n'}, | ||||
|         {"\\n", 2, '\n'},  {"tab", 3, '\t'}, {"\\t", 2, '\t'}, | ||||
|     }; | ||||
| #define LOOKUP_TABLE_SIZE (sizeof(LOOKUP_TABLE) / sizeof(LOOKUP_TABLE[0])) | ||||
|     struct ReadState start_state = *state; | ||||
|     popc(state); // # | ||||
|     const char *start = state->head; | ||||
|     while (!is_symbol_end(peekc(state))) { | ||||
|         popc(state); | ||||
|     } | ||||
|     size_t len = state->head - start; | ||||
|     for (size_t i = 0; i < LOOKUP_TABLE_SIZE; ++i) { | ||||
|         if (len == LOOKUP_TABLE[i].len | ||||
|             && strings_equal_nocase(start, LOOKUP_TABLE[i].name, len)) { | ||||
|             return make_lisp_integer(LOOKUP_TABLE[i].value); | ||||
|         } | ||||
|     } | ||||
|     READ_ERROR(&start_state, len, "unknown character liternal: %*s", (int) len, | ||||
|                start); | ||||
|     return Qnil; | ||||
| #undef LOOKUP_TABLE_SIZE | ||||
| } | ||||
|  | ||||
| #define INVALID_BASE -2 | ||||
| #define ANY_BASE     -1 | ||||
|  | ||||
| static bool is_base_char(int base, int c) { | ||||
|     c = tolower(c); | ||||
|     if (base == ANY_BASE) { | ||||
|         base = 10; | ||||
|     } | ||||
|     switch (base) { | ||||
|     case 16: | ||||
|         if (c >= 'a' || c <= 'f') { | ||||
|             return true; | ||||
|         } | ||||
|     case 10: | ||||
|     case 8: | ||||
|     case 2: | ||||
|         return c >= '0' && c <= '0' + base - 1; | ||||
|     default: | ||||
|         return false; | ||||
|     } | ||||
| } | ||||
|  | ||||
| static int parse_base(size_t left, const char *c) { | ||||
|     if (left >= 2 && *c == '2' && *(c + 1) == '#') { | ||||
|         return 2; | ||||
|     } else if (left >= 2 && *c == '8' && *(c + 1) == '#') { | ||||
|         return 8; | ||||
|     } else if (left >= 3 && *c == '1' && *(c + 1) == '0' && *(c + 2) == '#') { | ||||
|         return 10; | ||||
|     } else if (left >= 3 && *c == '1' && *(c + 1) == '6' && *(c + 2) == '#') { | ||||
|         return 16; | ||||
|     } else { | ||||
|         return INVALID_BASE; | ||||
|     } | ||||
| } | ||||
|  | ||||
| static LispVal *read_symbol(struct ReadState *state) { | ||||
|     const char *start = state->head; | ||||
|     while (!is_symbol_end(peekc(state))) { | ||||
|         popc(state); | ||||
|     } | ||||
|     return intern(start, state->head - start, false); | ||||
| } | ||||
|  | ||||
| static LispVal *read_number_or_symbol(struct ReadState *state, int base) { | ||||
|     size_t size_t_len = ((sizeof(size_t) * 8) / 3) + 1; | ||||
|     char fmt_buf[3 + size_t_len + 1]; | ||||
|     struct ReadState start_state = *state; | ||||
|     bool has_decimal = false; | ||||
|     const char *number_start = state->head; | ||||
|     const char *exp_start = NULL; | ||||
|     int c; | ||||
|     while (!is_symbol_end(c = peekc(state))) { | ||||
|         popc(state); | ||||
|         if (c == '#') { | ||||
|             if (base != ANY_BASE) { | ||||
|                 goto change_to_symbol; | ||||
|             } | ||||
|             base = parse_base(start_state.left, start_state.head); | ||||
|             if (base == INVALID_BASE) { | ||||
|                 goto change_to_symbol; | ||||
|             } | ||||
|             number_start = state->head; | ||||
|         } else if (c == '.') { | ||||
|             if (base != ANY_BASE || has_decimal || exp_start) { | ||||
|                 goto change_to_symbol; | ||||
|             } | ||||
|             has_decimal = true; | ||||
|         } else if ((c == '-' || c == '+')) { | ||||
|             if (state->head - 1 != number_start | ||||
|                 && state->head - 1 != exp_start) { | ||||
|                 goto change_to_symbol; | ||||
|             } | ||||
|             // fallthrough | ||||
|         } else if (!is_base_char(base, c)) { | ||||
|             if ((c == 'e' || c == 'E') && !exp_start && base == ANY_BASE) { | ||||
|                 exp_start = state->head; | ||||
|             } else { | ||||
|                 goto change_to_symbol; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|     size_t len = state->head - number_start; | ||||
|     // ceil(# bytes in size_t / 3) | ||||
|     // This works because log10(2^n) is O(n) for k=3 | ||||
|     if (has_decimal || exp_start) { | ||||
|         if (exp_start == state->head) { | ||||
|             goto change_to_symbol; | ||||
|         } | ||||
|         snprintf(fmt_buf, sizeof(fmt_buf), "%%%zuLf", len); | ||||
|         long double value; | ||||
|         sscanf(number_start, fmt_buf, &value); | ||||
|         return make_lisp_float(value); | ||||
|         // float | ||||
|     } else { | ||||
|         snprintf(fmt_buf, sizeof(fmt_buf), "%%%zujd", len); | ||||
|         intmax_t value; | ||||
|         sscanf(number_start, fmt_buf, &value); | ||||
|         return make_lisp_integer(value); | ||||
|         // int | ||||
|     } | ||||
|     abort(); | ||||
| change_to_symbol: | ||||
|     *state = start_state; | ||||
|     return read_symbol(state); | ||||
| } | ||||
|  | ||||
| static LispVal *read_internal(struct ReadState *state) { | ||||
|     SKIP_WHILE(isspace(peekc(state)), state); | ||||
|     int c = peekc(state); | ||||
|     // comment | ||||
|     while (c == ';') { | ||||
|         SKIP_WHILE(peekc(state) != '\n', state); | ||||
|         SKIP_WHITESPACE(state); | ||||
|         c = peekc(state); | ||||
|     } | ||||
|     switch (c) { | ||||
|         // list | ||||
|     case EOS: | ||||
|         return NULL; | ||||
|     case ')': | ||||
|         READ_ERROR(state, 1, "unmatched \")\""); | ||||
|         return Qnil; | ||||
|     case ']': | ||||
|         READ_ERROR(state, 1, "unmatched \"]\""); | ||||
|         return Qnil; | ||||
|     case '(': | ||||
|         return read_list(state); | ||||
|         // string | ||||
|     case '"': | ||||
|         return read_string(state); | ||||
|         // vector | ||||
|     case '[': | ||||
|         return read_vector(state); | ||||
|         // quoted form | ||||
|     case '\'': { | ||||
|         popc(state); // ' | ||||
|         LispVal *tail = read_internal(state); | ||||
|         return Fpair(Qquote, Fpair(tail, Qnil)); | ||||
|     } | ||||
|         // backquote | ||||
|     case '`': { | ||||
|         popc(state); // ` | ||||
|         ++state->backquote_level; | ||||
|         LispVal *tail = read_internal(state); | ||||
|         --state->backquote_level; | ||||
|         return Fpair(Qbackquote, Fpair(tail, Qnil)); | ||||
|     } | ||||
|         // comma | ||||
|     case ',': | ||||
|         popc(state); // , | ||||
|         if (state->backquote_level) { | ||||
|             --state->backquote_level; | ||||
|             LispVal *tail = read_internal(state); | ||||
|             ++state->backquote_level; | ||||
|             return Fpair(Qcomma, Fpair(tail, Qnil)); | ||||
|         } else { | ||||
|             READ_ERROR(state, 1, "comma not inside backquote"); | ||||
|             return Qnil; | ||||
|         } | ||||
|         // character literal | ||||
|     case '#': | ||||
|         return read_character(state); | ||||
|         // number | ||||
|     case '+': | ||||
|     case '-': | ||||
|     case '.': | ||||
|     case '1': | ||||
|     case '2': | ||||
|     case '3': | ||||
|     case '4': | ||||
|     case '5': | ||||
|     case '6': | ||||
|     case '7': | ||||
|     case '8': | ||||
|     case '9': | ||||
|     case '0': | ||||
|         return read_number_or_symbol(state, ANY_BASE); | ||||
|     // symbol | ||||
|     default: | ||||
|         return read_symbol(state); | ||||
|     } | ||||
| } | ||||
|  | ||||
| size_t read_from_buffer(const char *text, size_t length, LispVal **out) { | ||||
|     struct ReadState state = { | ||||
|         .head = text, | ||||
|         .left = length, | ||||
|         .off = 0, | ||||
|         .line = 1, | ||||
|         .col = 0, | ||||
|         .backquote_level = 0, | ||||
|     }; | ||||
|     LispVal *res = read_internal(&state); | ||||
|     if (!res) { | ||||
|         EOF_ERROR(&state); | ||||
|         *out = Qnil; | ||||
|     } else { | ||||
|         *out = res; | ||||
|     } | ||||
|     return state.off; | ||||
| } | ||||
							
								
								
									
										15
									
								
								src/read.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								src/read.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,15 @@ | ||||
| #ifndef INCLUDED_READ_H | ||||
| #define INCLUDED_READ_H | ||||
|  | ||||
| #include "lisp.h" | ||||
|  | ||||
| #include <stddef.h> | ||||
|  | ||||
| typedef enum { | ||||
|     SEVERITY_WARN, | ||||
|     SEVERITY_ERROR, | ||||
| } ReadErrorSeverity; | ||||
|  | ||||
| size_t read_from_buffer(const char *text, size_t length, LispVal **out); | ||||
|  | ||||
| #endif | ||||
		Reference in New Issue
	
	Block a user