From 700c3b1d1b3ed835ffab3fd502ab91baba8e2d1f Mon Sep 17 00:00:00 2001 From: Aryadev Chavali Date: Mon, 1 Sep 2025 21:26:01 +0100 Subject: Move implementation files into their own folder main.c and test.c generate binary executables so they can stay in the main folder, but the rest can go into their own dedicated folder to make it look nicer --- impl/constructor.c | 64 ++++++++++++ impl/stream.c | 296 +++++++++++++++++++++++++++++++++++++++++++++++++++++ impl/sv.c | 27 +++++ impl/symtable.c | 75 ++++++++++++++ impl/sys.c | 77 ++++++++++++++ impl/tag.c | 76 ++++++++++++++ impl/test.c | 238 ++++++++++++++++++++++++++++++++++++++++++ impl/vec.c | 94 +++++++++++++++++ 8 files changed, 947 insertions(+) create mode 100644 impl/constructor.c create mode 100644 impl/stream.c create mode 100644 impl/sv.c create mode 100644 impl/symtable.c create mode 100644 impl/sys.c create mode 100644 impl/tag.c create mode 100644 impl/test.c create mode 100644 impl/vec.c (limited to 'impl') diff --git a/impl/constructor.c b/impl/constructor.c new file mode 100644 index 0000000..4c8efa4 --- /dev/null +++ b/impl/constructor.c @@ -0,0 +1,64 @@ +/* Copyright (C) 2025 Aryadev Chavali + + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the Unlicense for details. + + * You may distribute and modify this code under the terms of the Unlicense, + * which you should have received a copy of along with this program. If not, + * please go to . + + * Created: 2025-08-20 + * Description: Lisp constructors/destructors + */ + +#include + +#include + +lisp_t *make_int(i64 i) +{ + return tag_int(i); +} + +lisp_t *cons(sys_t *sys, lisp_t *car, lisp_t *cdr) +{ + cons_t *cons = calloc(1, sizeof(*cons)); + cons->car = car; + cons->cdr = cdr; + + lisp_t *lcons = tag_cons(cons); + sys_register(sys, lcons); + return lcons; +} + +lisp_t *make_vec(sys_t *sys, u64 capacity) +{ + vec_t *vec = calloc(1, sizeof(*vec)); + vec_init(vec, capacity); + lisp_t *ptr = tag_vec(vec); + sys_register(sys, ptr); + return ptr; +} + +lisp_t *intern(sys_t *sys, sv_t sv) +{ + char *str = sym_table_find(&sys->symtable, sv); + return tag_sym(str); +} + +lisp_t *car(lisp_t *lsp) +{ + if (!IS_TAG(lsp, CONS)) + return NIL; + else + return CAR(lsp); +} + +lisp_t *cdr(lisp_t *lsp) +{ + if (!IS_TAG(lsp, CONS)) + return NIL; + else + return CDR(lsp); +} diff --git a/impl/stream.c b/impl/stream.c new file mode 100644 index 0000000..3f109be --- /dev/null +++ b/impl/stream.c @@ -0,0 +1,296 @@ +/* Copyright (C) 2025 Aryadev Chavali + + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the Unlicense for details. + + * You may distribute and modify this code under the terms of the Unlicense, + * which you should have received a copy of along with this program. If not, + * please go to . + + * Created: 2025-08-26 + * Description: Stream implementation + */ + +#include + +#include + +stream_err_t stream_init_string(stream_t *stream, char *name, sv_t contents) +{ + if (!stream) + return STREAM_ERR_INVALID_PTR; + name = name ? name : ""; + memset(stream, 0, sizeof(*stream)); + + stream->type = STREAM_TYPE_STRING; + stream->name = name; + stream->string = contents; + + return STREAM_ERR_OK; +} + +stream_err_t stream_init_file(stream_t *stream, char *name, FILE *pipe) +{ + if (!stream || !pipe) + return STREAM_ERR_INVALID_PTR; + name = name ? name : ""; + memset(stream, 0, sizeof(*stream)); + + stream->type = STREAM_TYPE_FILE; + stream->name = name; + stream->pipe.file = pipe; + + vec_init(&stream->pipe.cache, STREAM_DEFAULT_CHUNK); + + return STREAM_ERR_OK; +} + +void stream_stop(stream_t *stream) +{ + if (!stream) + return; + switch (stream->type) + { + case STREAM_TYPE_STRING: + // Nothing to do, all dealt with outside of stream + break; + case STREAM_TYPE_FILE: + // Must cleanup vector + vec_free(&stream->pipe.cache); + break; + } + memset(stream, 0, sizeof(*stream)); +} + +u64 stream_size(stream_t *stream) +{ + assert(stream); + switch (stream->type) + { + case STREAM_TYPE_STRING: + return stream->string.size; + case STREAM_TYPE_FILE: + return stream->pipe.cache.size; + default: + FAIL("Unreachable"); + return 0; + } +} + +bool stream_eos(stream_t *stream) +{ + assert(stream); + switch (stream->type) + { + case STREAM_TYPE_STRING: + return stream->position >= stream->string.size; + case STREAM_TYPE_FILE: + return feof(stream->pipe.file); + default: + FAIL("Unreachable"); + return 0; + } +} + +bool stream_eoc(stream_t *stream) +{ + assert(stream); + switch (stream->type) + { + case STREAM_TYPE_STRING: + return stream->position >= stream->string.size; + case STREAM_TYPE_FILE: + return stream->position >= stream->pipe.cache.size; + default: + FAIL("Unreachable"); + return 0; + } +} + +bool stream_chunk(stream_t *stream) +{ + assert(stream); + switch (stream->type) + { + case STREAM_TYPE_STRING: + // vacuously true + return true; + case STREAM_TYPE_FILE: + { + if (feof(stream->pipe.file)) + return false; + vec_ensure_free(&stream->pipe.cache, STREAM_DEFAULT_CHUNK); + int read = fread(vec_data(&stream->pipe.cache), 1, STREAM_DEFAULT_CHUNK, + stream->pipe.file); + stream->pipe.cache.size += read; + return true; + } + default: + FAIL("Unreachable"); + return 0; + } +} + +char stream_next(stream_t *stream) +{ + char c = stream_peek(stream); + if (c != '\0') + ++stream->position; + return c; +} + +char stream_peek(stream_t *stream) +{ + if (stream_eos(stream)) + return '\0'; + + switch (stream->type) + { + case STREAM_TYPE_STRING: + return stream->string.data[stream->position]; + case STREAM_TYPE_FILE: + { + // Cached already? We are done. + if (stream->position < stream->pipe.cache.size) + return ((char *)vec_data(&stream->pipe.cache))[stream->position]; + + // Try to read chunks in till we've reached it or we're at the end of the + // file. + for (bool read_chunk = stream_chunk(stream); + read_chunk && stream->position >= stream->pipe.cache.size; + read_chunk = stream_chunk(stream)) + continue; + + // Same principle as the stream_eos(stream) check. + if (stream->position >= stream->pipe.cache.size) + return '\0'; + return ((char *)vec_data(&stream->pipe.cache))[stream->position]; + } + default: + FAIL("Unreachable"); + return 0; + } +} + +bool stream_seek(stream_t *stream, i64 offset) +{ + if (offset < 0) + return stream_seek_backward(stream, offset * -1); + else if (offset > 0) + return stream_seek_forward(stream, offset); + else + // vacuously successful + return true; +} + +bool stream_seek_forward(stream_t *stream, u64 offset) +{ + if (stream_eos(stream)) + return false; + switch (stream->type) + { + case STREAM_TYPE_STRING: + { + if (stream->position + offset < stream->string.size) + { + stream->position += offset; + return true; + } + return false; + } + case STREAM_TYPE_FILE: + { + // Similar principle as stream_peek really... + + // Cached already? We are done. + if (stream->position + offset < stream->pipe.cache.size) + { + stream->position += offset; + return true; + } + + // Try to read chunks in till we've reached it or we're at the end of the + // file. + for (bool read_chunk = stream_chunk(stream); + read_chunk && stream->position + offset >= stream->pipe.cache.size; + read_chunk = stream_chunk(stream)) + continue; + + // Same principle as the stream_eos(stream) check. + if (stream->position + offset >= stream->pipe.cache.size) + return false; + stream->position += offset; + return true; + } + default: + FAIL("Unreachable"); + return 0; + } +} + +bool stream_seek_backward(stream_t *stream, u64 offset) +{ + assert(stream); + if (stream->position < offset) + return false; + stream->position -= offset; + return true; +} + +sv_t stream_substr(stream_t *stream, u64 size) +{ + if (stream_eos(stream)) + return SV(NULL, 0); + u64 current_position = stream->position; + bool successful = stream_seek_forward(stream, size); + // In case we did happen to move forward + stream->position = current_position; + if (!successful) + return SV(NULL, 0); + + char *ptr = NULL; + switch (stream->type) + { + case STREAM_TYPE_STRING: + ptr = stream->string.data; + break; + case STREAM_TYPE_FILE: + ptr = vec_data(&stream->pipe.cache); + break; + default: + FAIL("Unreachable"); + return SV(NULL, 0); + } + + return SV(ptr + stream->position, size); +} + +sv_t stream_substr_abs(stream_t *stream, u64 index, u64 size) +{ + + switch (stream->type) + { + case STREAM_TYPE_STRING: + if (index + size < stream_size(stream)) + return SV(stream->string.data + index, size); + return SV(NULL, 0); + case STREAM_TYPE_FILE: + { + if (index + size < stream_size(stream)) + return SV(vec_data(&stream->pipe.cache) + index, size); + // stream_size(stream) <= index + size => try reading chunks + for (bool read_chunk = stream_chunk(stream); + read_chunk && index + size >= stream->pipe.cache.size; + read_chunk = stream_chunk(stream)) + continue; + + if (index + size >= stream_size(stream)) + return SV(NULL, 0); + return SV(vec_data(&stream->pipe.cache) + index, size); + } + default: + assert("Unreachable"); + return SV(NULL, 0); + } +} diff --git a/impl/sv.c b/impl/sv.c new file mode 100644 index 0000000..97774c6 --- /dev/null +++ b/impl/sv.c @@ -0,0 +1,27 @@ +/* Copyright (C) 2025 Aryadev Chavali + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the Unlicense + * for details. + + * You may distribute and modify this code under the terms of the + * Unlicense, which you should have received a copy of along with this + * program. If not, please go to . + + * Created: 2025-08-21 + * Description: String views + */ + +#include +#include + +#include + +sv_t sv_copy(sv_t old) +{ + char *newstr = calloc(1, (old.size + 1) * sizeof(*newstr)); + memcpy(newstr, old.data, old.size); + newstr[old.size] = '\0'; + return SV(newstr, old.size); +} diff --git a/impl/symtable.c b/impl/symtable.c new file mode 100644 index 0000000..abef727 --- /dev/null +++ b/impl/symtable.c @@ -0,0 +1,75 @@ +/* Copyright (C) 2025 Aryadev Chavali + + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the Unlicense for details. + + * You may distribute and modify this code under the terms of the Unlicense, + * which you should have received a copy of along with this program. If not, + * please go to . + + * Created: 2025-08-19 + * Description: Symbol Table implementation + */ + +#include + +#include +#include + +u64 djb2(sv_t string) +{ + u64 hash = 5381; + for (u64 i = 0; i < string.size; ++i) + hash = string.data[i] + (hash + (hash << 5)); + return hash; +} + +void sym_table_init(sym_table_t *table) +{ + table->capacity = MAX(table->capacity, SYM_TABLE_INIT_SIZE); + table->count = 0; + vec_init(&table->entries, table->capacity * sizeof(sv_t)); +} + +char *sym_table_find(sym_table_t *table, sv_t sv) +{ + // Initialise the table if it's not done already + if (table->entries.capacity == 0) + sym_table_init(table); + + // TODO: Deal with resizing this when table->count > table->size / 2 + u64 index = djb2(sv) & (table->capacity - 1); + + for (sv_t comp = VEC_GET(&table->entries, sv_t)[index]; comp.data; index += 1, + index = index & (table->capacity - 1), + comp = VEC_GET(&table->entries, sv_t)[index]) + // Is it present in the table? + if (sv.size == comp.size && strncmp(sv.data, comp.data, sv.size) == 0) + break; + + // we couldn't find it in our linear search (worst case scenario) + if (!VEC_GET(&table->entries, sv_t)[index].data) + { + sv_t newsv = sv_copy(sv); + VEC_GET(&table->entries, sv_t)[index] = newsv; + ++table->count; + } + + return VEC_GET(&table->entries, sv_t)[index].data; +} + +void sym_table_cleanup(sym_table_t *table) +{ + // kill the data + sv_t current = {0}; + for (u64 i = 0; i < table->capacity; ++i) + { + current = VEC_GET(&table->entries, sv_t)[i]; + if (current.data) + free(current.data); + } + // kill the container + vec_free(&table->entries); + memset(table, 0, sizeof(*table)); +} diff --git a/impl/sys.c b/impl/sys.c new file mode 100644 index 0000000..4dcd664 --- /dev/null +++ b/impl/sys.c @@ -0,0 +1,77 @@ +/* Copyright (C) 2025 Aryadev Chavali + + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the Unlicense for details. + + * You may distribute and modify this code under the terms of the Unlicense, + * which you should have received a copy of along with this program. If not, + * please go to . + + * Created: 2025-08-20 + * Description: System management + */ + +#include + +#include +#include +#include + +void sys_init(sys_t *sys) +{ + memset(sys, 0, sizeof(*sys)); +} + +void sys_register(sys_t *sys, lisp_t *ptr) +{ + // Generate an unmanaged cons + cons_t *cons = calloc(1, sizeof(*cons)); + cons->car = ptr; + cons->cdr = sys->memory; + sys->memory = tag_cons(cons); +} + +void sys_cleanup(sys_t *sys) +{ + static_assert(NUM_TAGS == 5); + + sym_table_cleanup(&sys->symtable); + + if (!sys->memory) + return; + + // Iterate through each element of memory + for (lisp_t *cell = sys->memory, *next = cdr(cell); cell; + cell = next, next = cdr(next)) + { + // Only reason allocated exists is because we had to allocate memory on the + // heap for it. It's therefore enough to deal with only the allocated + // types. + lisp_t *allocated = car(cell); + switch (get_tag(allocated)) + { + case TAG_CONS: + // Delete the cons + free(as_cons(allocated)); + break; + case TAG_VEC: + { + vec_t *vec = as_vec(allocated); + vec_free(vec); + free(vec); + break; + } + case TAG_NIL: + case TAG_INT: + case TAG_SYM: + case NUM_TAGS: + // shouldn't be dealt with (either constant or dealt with elsewhere) + break; + } + + // Then free the current cell + free(as_cons(cell)); + } + memset(sys, 0, sizeof(*sys)); +} diff --git a/impl/tag.c b/impl/tag.c new file mode 100644 index 0000000..e6396cc --- /dev/null +++ b/impl/tag.c @@ -0,0 +1,76 @@ +/* Copyright (C) 2025 Aryadev Chavali + + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the Unlicense for details. + + * You may distribute and modify this code under the terms of the Unlicense, + * which you should have received a copy of along with this program. If not, + * please go to . + + * Created: 2025-08-19 + * Description: Pointer tagging + */ + +#include +#include + +#include + +lisp_t *tag_int(i64 i) +{ + return TAG((u64)i, INT); +} + +lisp_t *tag_sym(char *str) +{ + return TAG((u64)str, SYM); +} + +lisp_t *tag_vec(vec_t *vec) +{ + return TAG((u64)vec, VEC); +} + +lisp_t *tag_cons(cons_t *cons) +{ + return TAG((u64)cons, CONS); +} + +tag_t get_tag(lisp_t *lisp) +{ + static_assert(NUM_TAGS == 5); + if (!lisp) + return TAG_NIL; + else if (IS_TAG(lisp, INT)) + return TAG_INT; + + return (u64)lisp & 0xFF; +} + +i64 as_int(lisp_t *obj) +{ + assert(IS_TAG(obj, INT)); + u64 p_obj = (u64)obj; + return UNTAG(p_obj, INT) | // Delete the tag + (NTH_BYTE(p_obj, 7) & 0x80) << 56 // duplicate the MSB (preserve sign) + ; +} + +char *as_sym(lisp_t *obj) +{ + assert(IS_TAG(obj, SYM)); + return (char *)UNTAG(obj, SYM); +} + +cons_t *as_cons(lisp_t *obj) +{ + assert(IS_TAG(obj, CONS)); + return (cons_t *)UNTAG(obj, CONS); +} + +vec_t *as_vec(lisp_t *obj) +{ + assert(IS_TAG(obj, VEC)); + return (vec_t *)UNTAG(obj, VEC); +} diff --git a/impl/test.c b/impl/test.c new file mode 100644 index 0000000..e517286 --- /dev/null +++ b/impl/test.c @@ -0,0 +1,238 @@ +/* Copyright (C) 2025 Aryadev Chavali + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the Unlicense + * for details. + + * You may distribute and modify this code under the terms of the + * Unlicense, which you should have received a copy of along with this + * program. If not, please go to . + + * Created: 2025-08-21 + * Description: Tests + */ + +#include +#include + +#include + +#define TEST_PASSED() printf("[%s]: Pass\n", __func__) +#define TEST(NAME, COND) \ + do \ + { \ + if (!(COND)) \ + { \ + printf("[%s]: `%s` failed!\n", __func__, (NAME)); \ + assert(0); \ + } \ + } while (0) + +// Sample data +const char *unique_words[] = { + "bibendum", "etiam", "gravida", "dui", "cursus", + "purus", "diam", "phasellus", "nam", "fermentum", + "leo", "enim", "ac", "semper", "non", + "mauris", "proin", "tellus", "vivamus", "lobortis", + "lacus", "neque", "in", "nullam", "felis", + "orci", "pede", "tempus", "nec", "at", + "tortor", "massa", "sed", "magna", "eget", + "tempor", "velit", "imperdiet", "praesent", "volutpat", + "tristique", "id", "commodo", "aliquet", "quis", + "pellentesque", "eleifend", "porta", "nunc", "euismod", + "aliquam", "a", "erat", "dignissim", "ut", + "vitae", "vel", "donec", +}; + +char *words[] = { + "aliquam", "erat", "volutpat", "nunc", "eleifend", + "leo", "vitae", "magna", "in", "id", + "erat", "non", "orci", "commodo", "lobortis", + "proin", "neque", "massa", "cursus", "ut", + "gravida", "ut", "lobortis", "eget", "lacus", + "sed", "diam", "praesent", "fermentum", "tempor", + "tellus", "nullam", "tempus", "mauris", "ac", + "felis", "vel", "velit", "tristique", "imperdiet", + "donec", "at", "pede", "etiam", "vel", + "neque", "nec", "dui", "dignissim", "bibendum", + "vivamus", "id", "enim", "phasellus", "neque", + "orci", "porta", "a", "aliquet", "quis", + "semper", "a", "massa", "phasellus", "purus", + "pellentesque", "tristique", "imperdiet", "tortor", "nam", + "euismod", "tellus", "id", "erat", +}; + +char words_text[] = + "aliquam erat volutpat nunc eleifend leo vitae magna in id erat non orci " + "commodo lobortis proin neque massa cursus ut gravida ut lobortis eget " + "lacus sed diam praesent fermentum tempor tellus nullam tempus mauris ac " + "felis vel velit tristique imperdiet donec at pede etiam vel neque nec dui " + "dignissim bibendum vivamus id enim phasellus neque orci porta a aliquet " + "quis semper a massa phasellus purus pellentesque tristique imperdiet " + "tortor nam euismod tellus id erat"; + +char text[] = + "Pellentesque dapibus suscipit ligula. Donec posuere augue in quam. " + "Etiam vel tortor sodales tellus ultricies commodo. Suspendisse potenti. " + "Aenean in sem ac leo mollis blandit. Donec neque quam, dignissim in, " + "mollis nec, sagittis eu, wisi. Phasellus lacus. Etiam laoreet quam sed " + "arcu. Phasellus at dui in ligula mollis ultricies. Integer placerat " + "tristique nisl. Praesent augue. Fusce commodo. Vestibulum convallis, " + "lorem a tempus semper, dui dui euismod elit, vitae placerat urna tortor " + "vitae lacus. Nullam libero mauris, consequat quis, varius et, dictum id, " + "arcu. Mauris mollis tincidunt felis. Aliquam feugiat tellus ut neque. " + "Nulla facilisis, risus a rhoncus fermentum, tellus tellus lacinia purus, " + "et dictum nunc justo sit amet elit."; + +void symtable_test(void) +{ + sym_table_t table = {0}; + sym_table_init(&table); + for (u64 i = 0; i < ARRSIZE(words); ++i) + sym_table_find(&table, SV(words[i], strlen(words[i]))); + + TEST("|table|=|set(words)|", table.count == ARRSIZE(unique_words)); + TEST("|table| < |words|", table.count < ARRSIZE(words)); + + TEST_PASSED(); + + sym_table_cleanup(&table); +} + +void int_test(void) +{ + i64 ints[] = { + 1, -1, (1 << 10) - 1, (-1) * ((1 << 10) - 1), INT_MIN, INT_MAX, + }; + + for (u64 i = 0; i < ARRSIZE(ints); ++i) + { + i64 in = ints[i]; + lisp_t *lisp = make_int(in); + i64 out = as_int(lisp); + + TEST("in == out", in == out); + } + + TEST_PASSED(); +} + +void sym_test(void) +{ + sys_t system = {0}; + sys_init(&system); + for (u64 i = 0; i < ARRSIZE(words); ++i) + { + char *in = words[i]; + lisp_t *lisp = intern(&system, SV(in, strlen(in))); + char *out = as_sym(lisp); + TEST("unique pointers when interning a symbol", in != out); + TEST("same size", strlen(in) == strlen(out)); + TEST("same string", strncmp(in, out, strlen(in)) == 0); + } + TEST_PASSED(); + sys_cleanup(&system); +} + +void vec_test1(void) +{ + sys_t system = {0}; + sys_init(&system); + + // Generating a vector word by word + lisp_t *lvec = make_vec(&system, 0); + for (u64 i = 0; i < ARRSIZE(words); ++i) + { + char *word = words[i]; + vec_append(as_vec(lvec), word, strlen(word)); + if (i != ARRSIZE(words) - 1) + vec_append(as_vec(lvec), " ", 1); + } + vec_append(as_vec(lvec), "\0", 1); + + vec_t *vec = as_vec(lvec); + + TEST("same size vector", vec->size == ARRSIZE(words_text)); + TEST("same as actually concatenated string", + strncmp(vec_data(vec), words_text, vec->size) == 0); + + TEST_PASSED(); + sys_cleanup(&system); +} + +void vec_test2(void) +{ + sys_t system = {0}; + sys_init(&system); + // Generating substrings + struct Test + { + u64 start, size; + } tests[] = { + {0, 16}, + {0, 32}, + {32, 64}, + {0, ARRSIZE(text)}, + }; + + for (u64 i = 0; i < ARRSIZE(tests); ++i) + { + struct Test test = tests[i]; + const sv_t substr = SV(text + test.start, test.size); + const u64 size = test.size / 2; + + lisp_t *lvec = make_vec(&system, size); + vec_append(as_vec(lvec), text + test.start, test.size); + TEST("Vector grew", as_vec(lvec)->size > size); + TEST("Vector's substring is equivalent to the actual substring", + strncmp(vec_data(as_vec(lvec)), substr.data, substr.size) == 0); + } + + printf("[vec_test2]: Pass\n"); + sys_cleanup(&system); +} + +void cons_test(void) +{ + sys_t system = {0}; + sys_init(&system); + + // Let's make a list of words using `cons` + lisp_t *lisp = NIL; + for (u64 i = 0; i < ARRSIZE(words); ++i) + { + char *word = words[i]; + lisp_t *lword = intern(&system, SV(word, strlen(word))); + lisp = cons(&system, lword, lisp); + } + + // Make sure we've essentially reversed the `words` array + u64 i = ARRSIZE(words); + for (lisp_t *iter = lisp; iter; iter = cdr(iter)) + { + lisp_t *item = car(iter); + TEST("we've reversed the array", + strncmp(words[i - 1], as_sym(item), strlen(words[i - 1])) == 0); + i -= 1; + } + + TEST_PASSED(); + + sys_cleanup(&system); +} + +typedef void (*test_fn)(void); + +const test_fn TESTS[] = { + int_test, sym_test, vec_test1, vec_test2, cons_test, +}; + +int main(void) +{ + for (u64 i = 0; i < ARRSIZE(TESTS); ++i) + { + TESTS[i](); + } + return 0; +} diff --git a/impl/vec.c b/impl/vec.c new file mode 100644 index 0000000..12f577c --- /dev/null +++ b/impl/vec.c @@ -0,0 +1,94 @@ +/* Copyright (C) 2025 Aryadev Chavali + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the Unlicense + * for details. + + * You may distribute and modify this code under the terms of the + * Unlicense, which you should have received a copy of along with this + * program. If not, please go to . + + * Created: 2025-08-20 + * Description: Stable Vector implementation + */ + +#include +#include + +#include + +void vec_init(vec_t *vec, u64 size) +{ + memset(vec, 0, sizeof(*vec)); + if (!vec) + return; + else if (size <= VEC_INLINE_CAPACITY) + { + vec->is_inlined = 1; + vec->capacity = VEC_INLINE_CAPACITY; + vec->ptr = NULL; + } + else + { + vec->is_inlined = 0; + vec->capacity = size; + vec->ptr = calloc(1, vec->capacity); + } +} + +void vec_free(vec_t *vec) +{ + if (!vec) + return; + if (!vec->is_inlined && vec->ptr) + free(vec->ptr); + memset(vec, 0, sizeof(*vec)); +} + +void *vec_data(vec_t *vec) +{ + return vec->is_inlined ? vec->inlined : vec->ptr; +} + +void vec_ensure_free(vec_t *vec, u64 size) +{ + if (!vec) + return; + if (vec->capacity - vec->size < size) + { + vec->capacity = MAX(vec->capacity * VEC_MULT, vec->size + size); + if (vec->is_inlined) + { + // If we're inlined, we need to allocate on the heap now. So let's copy + // vec->inlined over to vec->ptr, then turn off inlining. + + // We need to do a two-way swap since vec->ptr and vec->inlined are taking + // up the same space. + u8 buffer[VEC_INLINE_CAPACITY]; + memcpy(buffer, vec->inlined, vec->size); + vec->ptr = calloc(1, vec->capacity); + memcpy(vec->ptr, buffer, vec->size); + vec->is_inlined = 0; + } + else + vec->ptr = realloc(vec->ptr, vec->capacity); + } +} + +void vec_append(vec_t *vec, const void *const ptr, u64 size) +{ + if (!vec) + return; + vec_ensure_free(vec, size); + memcpy(vec_data(vec) + vec->size, ptr, size); + vec->size += size; +} + +void vec_clone(vec_t *dest, vec_t *src) +{ + if (!src || !dest) + return; + vec_init(dest, src->capacity); + memcpy(vec_data(dest), vec_data(src), src->size); +} -- cgit v1.2.3-13-gbd6f