Compare commits

...

39 Commits

Author SHA1 Message Date
Aryadev Chavali
a7eeedf7fb test_stream: implement stream_test_seek 2026-02-06 04:54:23 +00:00
Aryadev Chavali
5bb83cfab7 stream: stream_seek will do clamped movement if offset is invalid
If a forward/backward offset is too big, we'll clamp to the edges of
the file rather than failing completely.  We return the number of
bytes moved so callers can still validate, but the stream API can now
deal with these situations a bit more effectively.
2026-02-06 04:53:10 +00:00
Aryadev Chavali
5a78b01a57 test_stream: don't write null terminator to mock file 2026-02-06 04:52:49 +00:00
Aryadev Chavali
2c1aebc8a5 test: seed random number generator 2026-02-06 04:52:40 +00:00
Aryadev Chavali
9452a14567 test_stream: make filename bigger, and increase the random alphabet 2026-02-06 04:34:10 +00:00
Aryadev Chavali
01fb0bf131 test_stream: randomise filename
Just to make sure it's not hardcoded or anything.
2026-02-05 20:51:45 +00:00
Aryadev Chavali
a662454ea7 test_stream: implement stream_test_peek_next 2026-02-05 20:47:28 +00:00
Aryadev Chavali
c503acb050 alisp.org: Mark off completed stream_test_file 2026-02-05 20:47:10 +00:00
Aryadev Chavali
fde3dbbf9a test_lisp_api: "cons'" -> "conses" 2026-02-05 20:36:02 +00:00
Aryadev Chavali
9e357cbc3b tests: TEST_START only logs if TEST_VERBOSE is enabled. 2026-02-05 20:34:49 +00:00
Aryadev Chavali
0b3d659f14 tests: TEST_INIT -> TEST_START, TEST_PASSED -> TEST_END 2026-02-05 20:34:29 +00:00
Aryadev Chavali
0e6a43ec5f test_stream: setup prologue and epilogue as fake tests in the suite
Standard old test functions, but they don't call TEST_INIT or
TEST_PASSED.  They're placed at the start and at the end of the test
array.

Those macros just do printing anyway, so they're not necessary.
2026-02-05 20:31:18 +00:00
Aryadev Chavali
2ddddf5774 test_stream: implement stream_test_file
We might need to setup a prelude for initialising a file in the
filesystem for testing here - not only does stream_test_file need it,
but I see later tests requiring an equivalence check for files and
strings (variants of a stream).
2026-02-05 20:22:03 +00:00
Aryadev Chavali
8d3f4f896f stream: do not initialise file streams with a non-empty vector
Because of the not_inlined trick, a 0 initialised SBO vector is
completely valid to use if required.  Future /vec_ensure/'s will deal
with it appropriately.  So there's no need to initialise the vector
ahead of time like this.
2026-02-05 18:53:13 +00:00
Aryadev Chavali
0318dcbb65 stream: Make stream name a constant cstr
We don't deal with the memory for it anyway.
2026-02-05 18:52:47 +00:00
Aryadev Chavali
f7cfe16c67 main: Put all variable declarations at start of main to ensure decl
There is a chance that /end/ is jumped to without the FILE pointer or
stream actually being declared.  This deals with that.
2026-02-05 18:39:49 +00:00
Aryadev Chavali
3612313e76 test: TEST_INIT macro as a prologue for any unit test 2026-02-05 07:37:36 +00:00
Aryadev Chavali
762dabd3e5 tests: Better suite creation
While the previous method of in-lining a stack allocated array of
tests into the suite struct declaration was nice, we had to update
size manually.

This macro will allow us to just append new tests to the suite without
having to care for that.  It generates a uniquely named variable for
the test array, then uses that test array in the suite declaration.
Nice and easy.
2026-02-05 06:44:35 +00:00
Aryadev Chavali
61efa91403 alisp.org: add TODOs for all the tests required for streams 2026-02-05 06:32:26 +00:00
Aryadev Chavali
3add9beb59 sv: fix possible runtime issue with NULL SV's in sv_copy 2026-02-05 06:17:52 +00:00
Aryadev Chavali
df1076f7d7 tests: enable STREAM_SUITE 2026-02-05 06:17:31 +00:00
Aryadev Chavali
a809d9de25 test_stream: Enable only stream_test_string 2026-02-05 06:17:21 +00:00
Aryadev Chavali
3b14144ccc test_stream: implement stream_test_string 2026-02-05 06:17:13 +00:00
Aryadev Chavali
bd838c02ab test_stream: basic skeleton 2026-02-05 06:06:03 +00:00
Aryadev Chavali
54e9edcba6 test_lisp_api: added sys_test 2026-02-05 05:48:00 +00:00
Aryadev Chavali
b32f420cb9 lisp: split off lisp_free as it's own function
lisp_free will do a shallow clean of any object, freeing its
associated memory.  It won't recur through any containers, nor will it
freakout if you give it something that is constant (symbols, small
integers, NIL, etc).
2026-02-05 05:39:35 +00:00
Aryadev Chavali
34d3417e74 symtable: sym_table_cleanup -> sym_table_free 2026-02-05 05:39:22 +00:00
Aryadev Chavali
66770f22d9 alisp.org: Added some tasks 2026-02-05 05:35:51 +00:00
Aryadev Chavali
3d0e373862 test_lisp_api: added sym_unique_test 2026-02-05 05:34:54 +00:00
Aryadev Chavali
0e8cdd7507 test_lisp_api: sym_test -> sym_fresh_test 2026-02-05 05:34:39 +00:00
Aryadev Chavali
4d693c8a92 test_lisp_api: int_test -> smi_test, added smi_oob_test 2026-02-05 05:24:34 +00:00
Aryadev Chavali
1c88253b3c tests: fix size of LISP_API_SUITE tests 2026-02-05 05:23:38 +00:00
Aryadev Chavali
de6fcd17ee Makefile: added mode flag for full logs
MODE=full will initialise a debug build with all logs, including test
logs.  Otherwise, MODE=debug just sets up standard debug build with
main logs but no testing logs.  MODE=release optimises and strips all
logs.
2026-02-05 05:17:25 +00:00
Aryadev Chavali
91264d96e4 test: Added definition to make default testing less verbose
TEST_VERBOSE is a preprocesser directive which TEST is dependent on.
By default it is 0, in which case TEST simply fails if the condition
is not true.  Otherwise, a full log (as done previously) is made.
2026-02-05 05:16:11 +00:00
Aryadev Chavali
d88d7f7f23 tests: c23 allows you to inline stack allocated arrays in struct decls 2026-02-05 05:10:19 +00:00
Aryadev Chavali
16be3392b0 tests: slight cleanliness 2026-02-05 05:10:12 +00:00
Aryadev Chavali
2705ef9bb7 tests: Meaningful and pretty logging for tests 2026-02-05 04:56:11 +00:00
Aryadev Chavali
45ef1fa01a tests: Added string view suite
sv_copy is the only function, but we may have others later.
2026-02-05 04:48:42 +00:00
Aryadev Chavali
9095b118fd tests: split of symtable testing into its own suite
makes sense to be there, not in the lisp API
2026-02-05 04:48:42 +00:00
17 changed files with 724 additions and 131 deletions

View File

@@ -12,8 +12,10 @@ RFLAGS=-O3
MODE=release
ifeq ($(MODE), release)
CFLAGS=$(GFLAGS) $(RFLAGS)
else
else ifeq ($(MODE), debug)
CFLAGS=$(GFLAGS) $(DFLAGS)
else ifeq ($(MODE), full)
CFLAGS=$(GFLAGS) $(DFLAGS) -DTEST_VERBOSE=1
endif
# Units to compile

View File

@@ -45,11 +45,61 @@ i.e. no parsing.
*** DONE Design what a "parser function" would look like
The general function is something like ~stream -> T | Err~. What
other state do we need to encode?
*** WIP Write a parser for integers
*** TODO Write a parser for integers
*** TODO Write a parser for symbols
*** TODO Write a parser for lists
*** TODO Write a parser for vectors
*** TODO Write the general parser
** WIP Unit tests :tests:
*** TODO Test streams
**** DONE Test file init
[[file:test/test_stream.c::void stream_test_file(void)]]
***** DONE Test successful init from real files
Ensure stream_size is 0 i.e. we don't read anything on creation.
Also ensure stream_eoc is false.
***** DONE Test failed init from fake files
**** DONE Test peeking and next
[[file:test/test_stream.c::void stream_test_peek_next(void)]]
- Peeking with bad streams ('\0' return)
- Peeking with good streams (no effect on position)
- Next with bad streams ('\0' return, no effect on position)
- Next with good streams (effects position)
- Peeking after next (should just work)
**** DONE Test seeking
[[file:test/test_stream.c::void stream_test_seek(void)]]
- Seeking forward/backward on a bad stream (should stop at 0)
- Seeking forward/backward too far (should clamp)
- Seeking forward/backward zero sum via relative index (stream_seek)
**** TODO Test substring
[[file:test/test_stream.c::void stream_test_substr(void)]]
- Substr on bad stream (NULL sv)
- Substr on bad position/size (NULL sv)
- Substr relative/absolute (good SV)
**** TODO Test till
[[file:test/test_stream.c::void stream_test_till(void)]]
- till on a bad stream (NULL SV)
- till on an ended stream (NULL SV)
- till on a stream with no items in search string (eoc)
- till on a stream with all items in search string (no effect)
- till on a stream with prefix being all search string (no effect)
- till on a stream with suffix being all search string (stops at
suffix)
**** TODO Test while
[[file:test/test_stream.c::void stream_test_while(void)]]
- while on a bad stream (NULL SV)
- while on an ended stream (NULL SV)
- while on a stream with no items in search string (no effect)
- while on a stream with all items in search string (eoc)
- while on a stream with prefix being all search string (effect)
- while on a stream with suffix being all search string (no effect)
**** TODO Test line_col
[[file:test/test_stream.c::void stream_test_line_col(void)]]
- line_col on bad stream (no effect on args)
- line_col on eoc stream (should go right to the end)
- line_col on random points in a stream
*** DONE Test system registration of allocated units
In particular, does clean up work as we expect? Do we have situations
where we may double free or not clean up something we should've?
** Backlog
*** TODO Design Big Integers
We currently have 62 bit integers implemented via immediate values
@@ -139,9 +189,6 @@ Latter approach time complexity:
Former approach is better time complexity wise, but latter is way
better in terms of simplicity of code. Must deliberate.
*** TODO Test system registration of allocated units :test:
In particular, does clean up work as we expect? Do we have situations
where we may double free or not clean up something we should've?
*** TODO Design Strings
We have ~sv_t~ so our basic C API is done. We just need pluggable
functions to construct and deconstruct strings as lisps.
@@ -160,4 +207,4 @@ Test if ~make_vec~ works with ~as_vec~, ~cons~ with ~as_cons~ AND
We may need to think of effective ways to deal with NILs in ~car~ and
~cdr~. Maybe make functions as well as the macros so I can choose
between them?
**** DONE Write more tests
*** DONE Write more tests

View File

@@ -49,6 +49,8 @@ vec_t *as_vec(lisp_t *);
lisp_t *car(lisp_t *);
lisp_t *cdr(lisp_t *);
void lisp_free(lisp_t *);
#endif
/* Copyright (C) 2026 Aryadev Chavali

View File

@@ -40,7 +40,7 @@ typedef struct
typedef struct
{
stream_type_t type;
char *name;
const char *name;
u64 position;
union
{
@@ -51,9 +51,9 @@ typedef struct
#define STREAM_DEFAULT_CHUNK 64
stream_err_t stream_init_string(stream_t *, char *, sv_t);
stream_err_t stream_init_pipe(stream_t *, char *, FILE *);
stream_err_t stream_init_file(stream_t *, char *, FILE *);
stream_err_t stream_init_string(stream_t *, const char *, sv_t);
stream_err_t stream_init_pipe(stream_t *, const char *, FILE *);
stream_err_t stream_init_file(stream_t *, const char *, FILE *);
void stream_stop(stream_t *);
// End of Content (i.e. we've consumed all cached content/file)
@@ -66,9 +66,9 @@ char stream_next(stream_t *);
// Peek current character, do not push position
char stream_peek(stream_t *);
// Move forward or backward in the stream, return success of operation
bool stream_seek(stream_t *, i64);
bool stream_seek_forward(stream_t *, u64);
bool stream_seek_backward(stream_t *, u64);
u64 stream_seek(stream_t *, i64);
u64 stream_seek_forward(stream_t *, u64);
u64 stream_seek_backward(stream_t *, u64);
// Return a relative substring of a given size
sv_t stream_substr(stream_t *, u64);

View File

@@ -22,7 +22,7 @@ typedef struct
void sym_table_init(sym_table_t *);
char *sym_table_find(sym_table_t *, sv_t);
void sym_table_cleanup(sym_table_t *);
void sym_table_free(sym_table_t *);
#endif

View File

@@ -26,7 +26,7 @@ void sys_free(sys_t *sys)
{
static_assert(NUM_TAGS == 5);
sym_table_cleanup(&sys->symtable);
sym_table_free(&sys->symtable);
if (sys->memory.size == 0)
return;
@@ -34,26 +34,7 @@ void sys_free(sys_t *sys)
for (size_t i = 0; i < VEC_SIZE(&sys->memory, lisp_t **); ++i)
{
lisp_t *allocated = VEC_GET(&sys->memory, i, lisp_t *);
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;
}
lisp_free(allocated);
}
// Free the container
@@ -110,6 +91,30 @@ lisp_t *cdr(lisp_t *lsp)
return CDR(lsp);
}
void lisp_free(lisp_t *item)
{
switch (get_tag(item))
{
case TAG_CONS:
// Delete the cons
free(as_cons(item));
break;
case TAG_VEC:
{
vec_t *vec = as_vec(item);
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;
}
}
/* Copyright (C) 2025, 2026 Aryadev Chavali
* This program is distributed in the hope that it will be useful, but WITHOUT

View File

@@ -23,7 +23,10 @@ void usage(FILE *fp)
int main(int argc, char *argv[])
{
int ret = 0;
int ret = 0;
FILE *pipe = NULL;
stream_t stream = {0};
if (argc == 1)
{
usage(stderr);
@@ -35,8 +38,6 @@ int main(int argc, char *argv[])
TODO("alisp doesn't support multiple files currently.");
}
FILE *fp = NULL;
stream_t stream = {0};
if (strncmp(argv[1], "--", 2) == 0)
{
stream_err_t err = stream_init_pipe(&stream, "stdin", stdin);
@@ -55,8 +56,8 @@ int main(int argc, char *argv[])
}
else
{
fp = fopen(argv[1], "rb");
stream_err_t err = stream_init_file(&stream, argv[1], fp);
pipe = fopen(argv[1], "rb");
stream_err_t err = stream_init_file(&stream, argv[1], pipe);
if (err)
{
fprintf(stderr, "ERROR: %s from `%s`\n", stream_err_to_cstr(err),
@@ -68,8 +69,8 @@ int main(int argc, char *argv[])
LOG("[INFO]: Initialised stream for `%s`\n", stream.name);
end:
if (fp)
fclose(fp);
if (pipe)
fclose(pipe);
stream_stop(&stream);
return ret;
}

View File

@@ -35,7 +35,8 @@ const char *stream_err_to_cstr(stream_err_t err)
}
}
stream_err_t stream_init_string(stream_t *stream, char *name, sv_t contents)
stream_err_t stream_init_string(stream_t *stream, const char *name,
sv_t contents)
{
if (!stream)
return STREAM_ERR_INVALID_PTR;
@@ -49,7 +50,7 @@ stream_err_t stream_init_string(stream_t *stream, char *name, sv_t contents)
return STREAM_ERR_OK;
}
stream_err_t stream_init_pipe(stream_t *stream, char *name, FILE *pipe)
stream_err_t stream_init_pipe(stream_t *stream, const char *name, FILE *pipe)
{
if (!stream)
return STREAM_ERR_INVALID_PTR;
@@ -68,7 +69,7 @@ stream_err_t stream_init_pipe(stream_t *stream, char *name, FILE *pipe)
return STREAM_ERR_OK;
}
stream_err_t stream_init_file(stream_t *stream, char *name, FILE *pipe)
stream_err_t stream_init_file(stream_t *stream, const char *name, FILE *pipe)
{
if (!stream)
return STREAM_ERR_INVALID_PTR;
@@ -82,8 +83,6 @@ stream_err_t stream_init_file(stream_t *stream, char *name, FILE *pipe)
stream->name = name;
stream->pipe.file = pipe;
vec_init(&stream->pipe.cache, STREAM_DEFAULT_CHUNK);
return STREAM_ERR_OK;
}
@@ -236,7 +235,7 @@ char stream_peek(stream_t *stream)
}
}
bool stream_seek(stream_t *stream, i64 offset)
u64 stream_seek(stream_t *stream, i64 offset)
{
if (offset < 0)
return stream_seek_backward(stream, offset * -1);
@@ -247,20 +246,20 @@ bool stream_seek(stream_t *stream, i64 offset)
return true;
}
bool stream_seek_forward(stream_t *stream, u64 offset)
u64 stream_seek_forward(stream_t *stream, u64 offset)
{
if (stream_eoc(stream))
return false;
return 0;
switch (stream->type)
{
case STREAM_TYPE_STRING:
{
if (stream->position + offset >= stream->string.size)
return false;
return 0;
stream->position += offset;
return true;
return offset;
}
case STREAM_TYPE_PIPE:
case STREAM_TYPE_FILE:
@@ -271,7 +270,7 @@ bool stream_seek_forward(stream_t *stream, u64 offset)
if (stream->position + offset < stream->pipe.cache.size)
{
stream->position += offset;
return true;
return offset;
}
// Try to read chunks in till we've reached it or we're at the end of the
@@ -283,9 +282,11 @@ bool stream_seek_forward(stream_t *stream, u64 offset)
// Same principle as the stream_eoc(stream) check.
if (stream->position + offset > stream->pipe.cache.size)
return false;
{
offset = stream->pipe.cache.size - stream->position;
}
stream->position += offset;
return true;
return offset;
}
default:
FAIL("Unreachable");
@@ -293,13 +294,16 @@ bool stream_seek_forward(stream_t *stream, u64 offset)
}
}
bool stream_seek_backward(stream_t *stream, u64 offset)
u64 stream_seek_backward(stream_t *stream, u64 offset)
{
assert(stream);
if (stream->position < offset)
return false;
{
offset = stream->position;
}
stream->position -= offset;
return true;
return offset;
}
sv_t stream_substr(stream_t *stream, u64 size)

View File

@@ -12,6 +12,10 @@
sv_t sv_copy(sv_t old)
{
if (old.size == 0)
return SV(old.data, 0);
else if (old.data == NULL)
return SV(NULL, old.size);
char *newstr = calloc(1, (old.size + 1) * sizeof(*newstr));
memcpy(newstr, old.data, old.size);
newstr[old.size] = '\0';

View File

@@ -54,7 +54,7 @@ char *sym_table_find(sym_table_t *table, sv_t sv)
return ENTRY_GET(table, index).data;
}
void sym_table_cleanup(sym_table_t *table)
void sym_table_free(sym_table_t *table)
{
// Iterate through the strings and free each of them.
sv_t current = {0};

View File

@@ -6,28 +6,33 @@
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "./data.h"
#include "./test.h"
#include "./test_lisp_api.c"
#include "./test_stream.c"
#include "./test_sv.c"
#include "./test_symtable.c"
#include "./test_vec.c"
test_suite_t SUITES[] = {
LISP_API_SUITE,
VEC_SUITE,
SV_SUITE, VEC_SUITE, SYMTABLE_SUITE, STREAM_SUITE, LISP_API_SUITE,
};
int main(void)
{
// Seed the pseudorandom gen for subsequent tests.
srand(time(NULL));
for (u64 i = 0; i < ARRSIZE(SUITES); ++i)
{
test_suite_t suite = SUITES[i];
printf("Suite [%s]\n", suite.name);
for (u64 j = 0; j < suite.size; ++j)
{
printf("[%s]: Running...\n", suite.tests[j].name);
suite.tests[j].fn();
}
}

View File

@@ -10,27 +10,50 @@
#include <alisp/alisp.h>
#define TEST_PASSED() printf("[%s]: Passed\n", __func__)
#define TEST(COND, ...) \
do \
{ \
bool cond = (COND); \
if (!cond) \
{ \
printf("\tFAIL: "); \
} \
else \
{ \
printf("\tPASS: "); \
} \
printf("%s => ", #COND); \
printf(__VA_ARGS__); \
printf("\n"); \
if (!cond) \
{ \
assert(0); \
} \
#ifndef TEST_VERBOSE
#define TEST_VERBOSE 0
#endif
#define TEST_END() printf("\t[%s]: Passed\n", __func__)
#define TEST_INFO(...) \
do \
{ \
printf("\tINFO: "); \
printf(__VA_ARGS__); \
} while (0);
#if TEST_VERBOSE
#define TEST_START() printf("\t[%s]: Running...\n", __func__)
#define TEST(COND, ...) \
do \
{ \
bool cond = (COND); \
if (!cond) \
{ \
printf("\t\tFAIL: "); \
} \
else \
{ \
printf("\t\tPASS: "); \
} \
printf(__VA_ARGS__); \
printf("\n\t\t [%s]\n", #COND); \
if (!cond) \
{ \
assert(0); \
} \
} while (0)
#else
#define TEST_START()
#define TEST(COND, ...) \
do \
{ \
if (!(COND)) \
{ \
assert(0); \
} \
} while (0)
#endif
typedef struct TestFn
{
@@ -47,8 +70,13 @@ typedef struct
const u64 size;
} test_suite_t;
#define MAKE_TEST_SUITE(NAME) \
{.name = #NAME, .tests = NAME, .size = ARRSIZE(NAME)}
#define MAKE_TEST_SUITE(NAME, DESC, ...) \
const test_fn NAME##_TESTS[] = {__VA_ARGS__}; \
const test_suite_t NAME = { \
.name = DESC, \
.tests = NAME##_TESTS, \
.size = ARRSIZE(NAME##_TESTS), \
}
#endif

View File

@@ -10,8 +10,11 @@
#include <alisp/lisp.h>
void int_test(void)
void smi_test(void)
{
TEST_START();
// Standard old testing, checking both sides of the number line and our set
// bounds.
i64 ints[] = {
1, -1, (1 << 10) - 1, (-1) * ((1 << 10) - 1), INT_MIN, INT_MAX,
};
@@ -25,31 +28,41 @@ void int_test(void)
TEST(in == out, "%ld == %ld", in, out);
}
TEST_PASSED();
TEST_END();
}
void symtable_test(void)
void smi_oob_test(void)
{
sym_table_t table = {0};
sym_table_init(&table);
for (u64 i = 0; i < ARRSIZE(words); ++i)
sym_table_find(&table, SV((char *)words[i], strlen(words[i])));
TEST_START();
// These are integers that are completely out of the bounds of our standard
// tagging system due to their size. We need to use big integers for this.
i64 ints[] = {
INT_MIN - 1,
INT_MAX + 1,
INT64_MIN,
INT64_MAX,
};
TEST(table.count == ARRSIZE(unique_words), "%lu == %lu", table.count,
ARRSIZE(unique_words));
for (u64 i = 0; i < ARRSIZE(ints); ++i)
{
i64 in = ints[i];
lisp_t *lisp = make_int(in);
i64 out = as_int(lisp);
TEST(table.count < ARRSIZE(unique_words), "%lu < %lu", table.count,
ARRSIZE(unique_words));
TEST(in != out, "%ld != %ld", in, out);
}
TEST_PASSED();
sym_table_cleanup(&table);
TEST_END();
}
void sym_test(void)
void sym_fresh_test(void)
{
TEST_START();
sys_t system = {0};
sys_init(&system);
// We expect every interned symbol to get a fresh allocation, but still be a
// valid representation of the original symbol.
for (u64 i = 0; i < ARRSIZE(words); ++i)
{
const char *in = words[i];
@@ -57,14 +70,48 @@ void sym_test(void)
char *out = as_sym(lisp);
TEST(in != out, "%p != %p", in, out);
TEST(strlen(in) == strlen(out), "%zu == %zu", strlen(in), strlen(out));
TEST(strncmp(in, out, strlen(in)) == 0, "%d", strncmp(in, out, strlen(in)));
TEST(strncmp(in, out, strlen(in)) == 0, "`%s` == `%s`", in, out);
}
TEST_PASSED();
sys_free(&system);
TEST_END();
}
void sym_unique_test(void)
{
TEST_START();
sys_t system = {0};
sys_init(&system);
sv_t symbols[] = {
SV("hello", 5),
SV("goodbye", 7),
SV("display", 7),
SV("@xs'a_sh;d::a-h]", 16),
};
lisp_t *ptrs[ARRSIZE(symbols)];
for (u64 i = 0; i < ARRSIZE(symbols); ++i)
{
ptrs[i] = intern(&system, symbols[i]);
TEST(ptrs[i] != 0, "%p (derived from `" PR_SV "`) is not NIL",
(void *)ptrs[i], SV_FMT(symbols[i]));
}
for (u64 i = 0; i < ARRSIZE(symbols); ++i)
{
lisp_t *newptr = intern(&system, symbols[i]);
TEST(newptr == ptrs[i], "interning again (%p) gives us the same (%p)",
(void *)newptr, (void *)ptrs[i]);
}
sys_free(&system);
TEST_END();
}
void cons_test(void)
{
TEST_START();
sys_t system = {0};
sys_init(&system);
@@ -77,7 +124,13 @@ void cons_test(void)
lisp = cons(&system, lword, lisp);
}
// Make sure we've essentially reversed the `words` array
/*
As we've cons'd each word, we'd expect the order to be reversed. This test
will allow us to verify:
1) words have actually been added to the linked list.
2) words are in the order we expect.
in one go.
*/
u64 i = ARRSIZE(words);
for (lisp_t *iter = lisp; iter; iter = cdr(iter), --i)
{
@@ -89,22 +142,61 @@ void cons_test(void)
TEST(strncmp(expected, got, size) == 0, "%s == %s", expected, got);
}
TEST_PASSED();
sys_free(&system);
TEST_END();
}
const test_fn TESTS_LISP_API[] = {
MAKE_TEST_FN(int_test),
MAKE_TEST_FN(sym_test),
MAKE_TEST_FN(cons_test),
};
void sys_test(void)
{
TEST_START();
sys_t sys = {0};
sys_init(&sys);
u64 old_memory_size = sys.memory.size;
const test_suite_t LISP_API_SUITE = {
.name = "Lisp API Tests",
.tests = TESTS_LISP_API,
.size = ARRSIZE(TESTS_LISP_API),
};
// Creating integers doesn't affect memory size
(void)make_int(2000);
TEST(sys.memory.size == old_memory_size,
"Making integers doesn't affect system memory size");
// Creating symbols won't affect memory size, but does affect the symbol table
(void)intern(&sys, SV("hello world!", 12));
TEST(sys.memory.size == old_memory_size,
"Interning doesn't affect system memory size");
TEST(sys.symtable.count > 0, "Interning affects symbol table");
// Creating conses do affect memory size
(void)cons(&sys, make_int(1), make_int(2));
TEST(sys.memory.size > 0, "Creating conses affects memory size");
old_memory_size = sys.memory.size;
(void)cons(&sys, intern(&sys, SV("test", 4)), NIL);
TEST(sys.memory.size > old_memory_size,
"Creating conses back to back affects memory size");
old_memory_size = sys.memory.size;
// Creating vectors does affect memory size
(void)make_vec(&sys, 8);
TEST(sys.memory.size > old_memory_size,
"Creating vectors (size 8) affects memory size");
old_memory_size = sys.memory.size;
(void)make_vec(&sys, 1000);
TEST(sys.memory.size > old_memory_size,
"Creating vectors (size 1000) affects memory size");
old_memory_size = sys.memory.size;
sys_free(&sys);
TEST(sys.memory.size == 0, "sys_free cleans up memory (shallow check)");
TEST(sys.symtable.count == 0, "sys_free cleans up symtable (shallow check)");
TEST_END();
}
MAKE_TEST_SUITE(LISP_API_SUITE, "LISP API Tests",
MAKE_TEST_FN(smi_test), MAKE_TEST_FN(smi_oob_test),
MAKE_TEST_FN(sym_fresh_test), MAKE_TEST_FN(sym_unique_test),
MAKE_TEST_FN(cons_test), MAKE_TEST_FN(sys_test), );
/* Copyright (C) 2026 Aryadev Chavali

321
test/test_stream.c Normal file
View File

@@ -0,0 +1,321 @@
/* test_stream.c: Stream tests
* Created: 2026-02-05
* Author: Aryadev Chavali
* License: See end of file
* Commentary:
*/
#include <malloc.h>
#include <stdio.h>
#include "./data.h"
#include "./test.h"
#include <alisp/stream.h>
#include <string.h>
char valid_filename[50];
FILE *valid_fp = NULL;
FILE *invalid_fp = NULL;
void stream_test_prologue(void)
{
const char filename_prefix[] = "build/stream_test_";
valid_filename[ARRSIZE(valid_filename) - 1] = '\0';
memcpy(valid_filename, filename_prefix, ARRSIZE(filename_prefix) - 1);
for (u64 i = ARRSIZE(filename_prefix) - 1; i < ARRSIZE(valid_filename) - 1;
++i)
{
u8 num = (rand() % 36);
if (num < 26)
{
valid_filename[i] = num + 'a';
}
else
{
valid_filename[i] = num + '0';
}
}
TEST_INFO("Creating file named `%.*s`\n", (int)ARRSIZE(valid_filename),
valid_filename);
valid_fp = fopen(valid_filename, "wb");
// This should do a few things for us
// 1) Create a file, or clear the contents of it if it exists already.
// 2) Write some content to it.
assert(valid_fp);
fwrite(words_text, ARRSIZE(words_text) - 1, 1, valid_fp);
fclose(valid_fp);
valid_fp = fopen(valid_filename, "rb");
assert(valid_fp);
invalid_fp = NULL;
}
void stream_test_epilogue(void)
{
TEST_INFO("Freeing resources and deleting file `%s`\n", valid_filename);
assert(valid_fp);
fclose(valid_fp);
remove(valid_filename);
}
void stream_test_string(void)
{
TEST_START();
sv_t test_strings[] = {
SV("hello, world!", 13),
SV("another string", 14),
SV((char *)text, ARRSIZE(text) / 2),
};
for (u64 i = 0; i < ARRSIZE(test_strings); ++i)
{
sv_t copy = sv_copy(test_strings[i]);
stream_t stream = {0};
stream_err_t err = stream_init_string(&stream, NULL, test_strings[i]);
TEST(err == STREAM_ERR_OK, "Stream initialising did not fail: %s",
stream_err_to_cstr(err));
TEST(stream_size(&stream) == test_strings[i].size,
"Stream size is always string size (%lu == %lu)", stream_size(&stream),
test_strings[i].size);
TEST(!stream_eoc(&stream), "Not end of content already");
stream_stop(&stream);
TEST(strncmp(copy.data, test_strings[i].data, copy.size) == 0,
"Freeing a stream does not free the underlying memory it was derived "
"from");
free(copy.data);
}
stream_t stream = {0};
stream_err_t err = stream_init_string(&stream, NULL, SV(NULL, 0));
TEST(err == STREAM_ERR_OK, "NULL stream initialising did not fail: %s",
stream_err_to_cstr(err));
TEST(stream_size(&stream) == 0, "NULL stream size is 0");
TEST(stream_eoc(&stream), "NULL stream is always at end of content");
stream_stop(&stream);
TEST_END();
}
void stream_test_file(void)
{
TEST_START();
// Test that initialising works correctly
{
stream_t stream = {0};
{
stream_err_t err = stream_init_file(&stream, valid_filename, valid_fp);
TEST(err == STREAM_ERR_OK, "Expected initialisating to be okay: %s",
stream_err_to_cstr(err));
}
TEST(stream_size(&stream) == 0, "Stream doesn't read on init: size = %lu",
stream_size(&stream));
TEST(!stream_eoc(&stream), "Stream should not be at the EoC from init.");
}
// try to initialise the stream again but against a nonexistent file - we're
// expecting an error.
{
stream_t stream = {0};
{
stream_err_t err = stream_init_file(&stream, NULL, invalid_fp);
TEST(err != STREAM_ERR_OK, "Expected initialisating to not be okay: %s",
stream_err_to_cstr(err));
}
}
TEST_END();
}
void stream_test_peek_next(void)
{
TEST_START();
// Valid streams
{
stream_t stream = {0};
stream_init_file(&stream, valid_filename, valid_fp);
u64 old_position = stream.position;
char c1 = stream_peek(&stream);
TEST(c1 != '\0', "Peek should provide a normal character (%c)", c1);
TEST(stream.position == old_position,
"Peek should not shift the position (%lu -> %lu)", old_position,
stream.position);
char c2 = stream_next(&stream);
TEST(c2 != '\0', "Next should provide a normal character (%c)", c2);
TEST(stream.position > old_position,
"Next should shift the position (%lu -> %lu)", old_position,
stream.position);
TEST(c2 != c1,
"Next should yield a different character (%c) to the previous peek "
"(%c)",
c2, c1);
char c3 = stream_peek(&stream);
TEST(c3 == c2,
"Peeking should yield the same character (%c) as the previous next "
"(%c)",
c3, c2);
stream_stop(&stream);
}
// Invalid streams
{
stream_t stream = {0};
stream_init_file(&stream, NULL, invalid_fp);
char c = stream_peek(&stream);
TEST(c == '\0', "Invalid streams should have an invalid peek (%c)", c);
u64 old_position = stream.position;
c = stream_next(&stream);
TEST(c == '\0', "Invalid streams should have an invalid next (%c)", c);
TEST(old_position == stream.position,
"Next on an invalid stream should not affect position (%lu -> %lu)",
old_position, stream.position);
stream_stop(&stream);
}
TEST_END();
}
void stream_test_seek(void)
{
TEST_START();
// Seeking on invalid streams
{
stream_t stream = {0};
stream_init_file(&stream, NULL, invalid_fp);
u64 old_position = stream.position;
TEST(!stream_seek_forward(&stream, 1),
"Shouldn't be possible to seek forward on an invalid stream.");
TEST(old_position == stream.position,
"Position shouldn't be affected when seeking forward on an invalid "
"stream"
"(%lu -> %lu)",
old_position, stream.position);
TEST(!stream_seek_backward(&stream, 1),
"Shouldn't be possible to seek backward on an invalid stream.");
TEST(old_position == stream.position,
"Position shouldn't be affected when seeking backward on an invalid "
"stream (%lu -> %lu)",
old_position, stream.position);
stream_stop(&stream);
}
// Valid streams
{
stream_t stream = {0};
stream_init_file(&stream, valid_filename, valid_fp);
u64 old_position = stream.position;
TEST(stream_seek_forward(&stream, 1),
"Okay to seek forward on a valid stream.");
TEST(old_position < stream.position,
"Position should be greater than before when seeking forward on a "
"valid stream (%lu -> %lu)",
old_position, stream.position);
TEST(stream_seek_backward(&stream, 1),
"Okay to seek backward on a valid stream.");
TEST(old_position == stream.position,
"stream_seek_forward and stream_seek_backward are inverse operations");
u64 forward_offset = stream_seek_forward(&stream, ARRSIZE(words_text) * 2);
TEST(forward_offset < ARRSIZE(words_text) * 2,
"Forward seeking by offsets greater than file size clamps (%lu "
"clamps to %lu)",
ARRSIZE(words_text) * 2, forward_offset);
u64 file_size = stream_size(&stream);
u64 backward_offset = stream_seek_backward(&stream, file_size + 1);
TEST(backward_offset == file_size,
"Backward seeking by offsets greater than file size clamps (%lu "
"clamps to %lu)",
file_size + 1, backward_offset);
TEST(stream.position == 0,
"Clamped forward and clamped backward seeking "
"leads to start of stream (position=%lu)",
stream.position);
i64 r_forward_offset = (rand() % (file_size - 1)) + 1;
i64 r_backward_offset = (rand() % (file_size - 1)) + 1;
while (r_backward_offset >= r_forward_offset)
r_backward_offset = (rand() % (file_size - 1)) + 1;
TEST(stream_seek(&stream, r_forward_offset) == (u64)r_forward_offset,
"Seeking by a random positive offset (%lu) is valid",
r_forward_offset);
TEST(stream_seek(&stream, -r_backward_offset) == (u64)r_backward_offset,
"Seeking backward by a random negative offset (%lu) is valid",
r_backward_offset);
TEST(
(i64)stream.position == r_forward_offset - r_backward_offset,
"Stream position (%lu) is exactly shifted by seeking offsets described "
"above.",
stream.position);
stream_stop(&stream);
}
TEST_END();
}
void stream_test_substr(void)
{
TEST_START();
TODO("Not implemented");
}
void stream_test_till(void)
{
TEST_START();
TODO("Not implemented");
}
void stream_test_while(void)
{
TEST_START();
TODO("Not implemented");
}
void stream_test_line_col(void)
{
TEST_START();
TODO("Not implemented");
}
MAKE_TEST_SUITE(STREAM_SUITE, "Stream Tests",
MAKE_TEST_FN(stream_test_prologue),
MAKE_TEST_FN(stream_test_string),
MAKE_TEST_FN(stream_test_file),
MAKE_TEST_FN(stream_test_peek_next),
MAKE_TEST_FN(stream_test_seek),
MAKE_TEST_FN(stream_test_epilogue), );
/* Copyright (C) 2026 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 GNU General Public License Version 2 for
* details.
* You may distribute and modify this code under the terms of the GNU General
* Public License Version 2, which you should have received a copy of along with
* this program. If not, please go to <https://www.gnu.org/licenses/>.
*/

45
test/test_sv.c Normal file
View File

@@ -0,0 +1,45 @@
/* test_sv.c: String View tests
* Created: 2026-02-05
* Author: Aryadev Chavali
* License: See end of file
* Commentary:
*/
#include <assert.h>
#include <malloc.h>
#include "./data.h"
#include "./test.h"
void sv_copy_test(void)
{
TEST_START();
static_assert(ARRSIZE(unique_words) > 3, "Expected at least 3 unique words");
for (u64 i = 0; i < 3; ++i)
{
sv_t word = SV((char *)unique_words[i], strlen(unique_words[i]));
sv_t copy = sv_copy(word);
TEST(word.data != copy.data, "%p != %p", word.data, copy.data);
TEST(word.size == copy.size, "%lu == %lu", word.size, copy.size);
TEST(strncmp(word.data, copy.data, copy.size) == 0, "`%s` == `%s`",
word.data, copy.data);
// Obviously we can't just have this lying around.
free(copy.data);
}
}
MAKE_TEST_SUITE(SV_SUITE, "String View Tests", MAKE_TEST_FN(sv_copy_test), );
/* Copyright (C) 2026 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 GNU General Public License Version 2 for
* details.
* You may distribute and modify this code under the terms of the GNU General
* Public License Version 2, which you should have received a copy of along with
* this program. If not, please go to <https://www.gnu.org/licenses/>.
*/

41
test/test_symtable.c Normal file
View File

@@ -0,0 +1,41 @@
/* test_symtable.c: Symbol table tests
* Created: 2026-02-05
* Author: Aryadev Chavali
* License: See end of file
* Commentary:
*/
#include "./data.h"
#include "./test.h"
void symtable_test(void)
{
TEST_START();
sym_table_t table = {0};
sym_table_init(&table);
for (u64 i = 0; i < ARRSIZE(words); ++i)
sym_table_find(&table, SV((char *)words[i], strlen(words[i])));
TEST(table.count == ARRSIZE(unique_words), "%lu == %lu", table.count,
ARRSIZE(unique_words));
sym_table_free(&table);
TEST_END();
}
MAKE_TEST_SUITE(SYMTABLE_SUITE, "Symbol Table Tests",
MAKE_TEST_FN(symtable_test), );
/* Copyright (C) 2026 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 GNU General Public License Version 2 for
* details.
* You may distribute and modify this code under the terms of the GNU General
* Public License Version 2, which you should have received a copy of along with
* this program. If not, please go to <https://www.gnu.org/licenses/>.
*/

View File

@@ -8,8 +8,9 @@
#include "./data.h"
#include "./test.h"
void vec_test1(void)
void vec_test_concat(void)
{
TEST_START();
sys_t system = {0};
sys_init(&system);
@@ -28,15 +29,17 @@ void vec_test1(void)
TEST(vec->size == ARRSIZE(words_text), "%lu == %lu", vec->size,
ARRSIZE(words_text));
TEST(strncmp((char *)vec_data(vec), words_text, vec->size) == 0, "%d",
strncmp((char *)vec_data(vec), words_text, vec->size));
TEST(strncmp((char *)vec_data(vec), words_text, vec->size) == 0,
"%p@%lu == %p@%lu", (char *)vec_data(vec), vec->size, words_text,
strlen(words_text));
TEST_PASSED();
sys_free(&system);
TEST_END();
}
void vec_test2(void)
void vec_test_substr(void)
{
TEST_START();
sys_t system = {0};
sys_init(&system);
// Generating substrings
@@ -60,24 +63,17 @@ void vec_test2(void)
vec_append(as_vec(lvec), text + test.start, test.size);
TEST(as_vec(lvec)->size > size, "%lu > %lu", as_vec(lvec)->size, size);
TEST(strncmp((char *)vec_data(as_vec(lvec)), substr.data, substr.size) == 0,
"%d",
strncmp((char *)vec_data(as_vec(lvec)), substr.data, substr.size));
"%p@%lu == %p@%lu", (char *)vec_data(as_vec(lvec)), as_vec(lvec)->size,
substr.data, substr.size);
}
TEST_PASSED();
sys_free(&system);
TEST_END();
}
const test_fn TESTS_VEC[] = {
MAKE_TEST_FN(vec_test1),
MAKE_TEST_FN(vec_test2),
};
MAKE_TEST_SUITE(VEC_SUITE, "Vector Tests",
const test_suite_t VEC_SUITE = {
.name = "Vector Tests",
.tests = TESTS_VEC,
.size = ARRSIZE(TESTS_VEC),
};
MAKE_TEST_FN(vec_test_concat), MAKE_TEST_FN(vec_test_substr), );
/* Copyright (C) 2026 Aryadev Chavali