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