diff --git a/Makefile b/Makefile index 88d71fb..ece90db 100644 --- a/Makefile +++ b/Makefile @@ -3,6 +3,8 @@ all: c2 c2: v0/bin/c2 - # @copilot copy the compiler + cp $< $@ + +test:: include v0/include.mk \ No newline at end of file diff --git a/v0/include.mk b/v0/include.mk index 04f102e..ac1e3c0 100644 --- a/v0/include.mk +++ b/v0/include.mk @@ -1,14 +1,25 @@ -V0_SRC := # @copilot include everything in src -V0_TEST := # @copilot include everything in test +V0_SRC := $(wildcard v0/src/*.c) +// +V0_TEST := $(filter-out v0/tests/test_buffer.c,$(wildcard v0/tests/*.c)) -V0_SRC_OBJ := # @copilot implement. exampleL v0/src/main.c -> v0/bin/src/main.o -V0_TEST_OBJ := # @copilot +V0_SRC_OBJ := $(patsubst v0/%.c,v0/bin/%.o,$(V0_SRC)) +V0_TEST_OBJ := $(patsubst v0/%.c,v0/bin/%.o,$(V0_TEST)) -v0/bin/c2: $(V0_SRC OBJ) - # @copilot implement +v0/bin/c2: $(V0_SRC_OBJ) + $(CC) $(CFLAGS) -o $@ $^ -v0/bin/test: $(V0_SRC_TEST) - # @copilot implement +V0_SRC_OBJ_NO_MAIN := $(filter-out v0/bin/src/main.o,$(V0_SRC_OBJ)) -# @copilot add target that builds each .c file into a .o file. -# Ensure header dependencies are tracked correctly. +v0/bin/test: $(V0_SRC_OBJ_NO_MAIN) $(V0_TEST_OBJ) + $(CC) $(CFLAGS) -o $@ $^ + +test:: v0/bin/test + v0/bin/test + +# Build each .c file into a .o file, tracking header dependencies. +v0/bin/%.o: v0/%.c + @mkdir -p $(dir $@) + $(CC) $(CFLAGS) -MMD -MP -c $< -o $@ + +-include $(V0_SRC_OBJ:.o=.d) +-include $(V0_TEST_OBJ:.o=.d) diff --git a/v0/src/buffer.c b/v0/src/buffer.c new file mode 100644 index 0000000..f7ccf23 --- /dev/null +++ b/v0/src/buffer.c @@ -0,0 +1,63 @@ +#include "buffer.h" + +#include +#include + +typedef enum { + BUFFER_FILE, + BUFFER_STRING +} BufferType; + +struct Buffer { + BufferType type; + union { + FILE* file; + struct { + const char* data; + size_t pos; + } string; + } source; +}; + +Buffer* buffer_open_file(const char* path) { + FILE* f = fopen(path, "r"); + if (f == NULL) + return NULL; + Buffer* buf = malloc(sizeof(Buffer)); + if (buf == NULL) { + fclose(f); + return NULL; + } + buf->type = BUFFER_FILE; + buf->source.file = f; + return buf; +} + +Buffer* buffer_open_string(const char* string) { + Buffer* buf = malloc(sizeof(Buffer)); + if (buf == NULL) + return NULL; + buf->type = BUFFER_STRING; + buf->source.string.data = string; + buf->source.string.pos = 0; + return buf; +} + +void buffer_close(Buffer* buffer) { + if (buffer->type == BUFFER_FILE) + fclose(buffer->source.file); + free(buffer); +} + +char buffer_read(Buffer* buffer) { + if (buffer->type == BUFFER_FILE) { + int c = fgetc(buffer->source.file); + return c == EOF ? (char)-1 : (char)c; + } else { + char c = buffer->source.string.data[buffer->source.string.pos]; + if (c == '\0') + return (char)-1; + buffer->source.string.pos++; + return c; + } +} diff --git a/v0/tests/test.c b/v0/tests/test.c index 71053b3..78c7c1e 100644 --- a/v0/tests/test.c +++ b/v0/tests/test.c @@ -1,24 +1,65 @@ #include "test.h" +#include "../src/buffer.h" +#include +#include -// @copilot implement fail() +static jmp_buf s_testJmp; +static const char* s_failMsg; + +void fail(const char* msg) { + s_failMsg = msg; + longjmp(s_testJmp, 1); +} + +typedef struct { + const char* name; + Test func; +} TestCase; + +#include "test_buffer.c" static int s_totalTests; static int s_greenTests; -static Test[] = { - // @copilot add all tests +static TestCase s_tests[] = { + {"buffer_string_reads_chars", test_buffer_string_reads_chars}, + {"buffer_string_eof", test_buffer_string_eof}, + {"buffer_string_eof_after_content", test_buffer_string_eof_after_content}, + {"buffer_file_reads_chars", test_buffer_file_reads_chars}, + {"buffer_file_open_fail", test_buffer_file_open_fail}, }; int main(int argc, char** argv) { - /* - @copilot loop over each test. - If the test is good, count the test as part of green test. - Otherwise, go to the next one. + (void)argc; + (void)argv; - The start of the test must log the name of the test method. - After the test, log either "[OK]" or "[FAIL]". + s_totalTests = sizeof(s_tests) / sizeof(s_tests[0]); + s_greenTests = 0; - At the very end, log the name of each failed test and the total - number of green tests compared to the total. - */ + const char* failedTests[s_totalTests + 1]; + int failedCount = 0; + + for (int i = 0; i < s_totalTests; i++) { + printf("%s...", s_tests[i].name); + s_failMsg = NULL; + + if (setjmp(s_testJmp) == 0) { + s_tests[i].func(); + printf(" [OK]\n"); + s_greenTests++; + } else { + printf(" [FAIL]: %s\n", s_failMsg ? s_failMsg : ""); + failedTests[failedCount++] = s_tests[i].name; + } + } + + if (failedCount > 0) { + printf("\nFailed tests:\n"); + for (int i = 0; i < failedCount; i++) { + printf(" - %s\n", failedTests[i]); + } + } + + printf("\n%d/%d tests passed.\n", s_greenTests, s_totalTests); + return failedCount > 0 ? 1 : 0; } \ No newline at end of file diff --git a/v0/tests/test.h b/v0/tests/test.h index 095927a..92ba2de 100644 --- a/v0/tests/test.h +++ b/v0/tests/test.h @@ -4,8 +4,7 @@ #ifndef TEST_H #define TEST_H -// @copilot fix the syntax. This is a typedef for any function taking a void and returning void. -typedef Test void(void); +typedef void (*Test)(void); /** * Fails a test. diff --git a/v0/tests/test_buffer.c b/v0/tests/test_buffer.c new file mode 100644 index 0000000..9644be5 --- /dev/null +++ b/v0/tests/test_buffer.c @@ -0,0 +1,43 @@ +#include "test.h" +#include "../src/buffer.h" +#include + +// @copilot add test for reading from a file. +// Create test_buffer_xyz.txt if you need multiple, +// otherwise simply test_buffer.txt + +static void test_buffer_string_reads_chars(void) { + Buffer* buf = buffer_open_string("hi"); + if (buffer_read(buf) != 'h') fail("expected 'h'"); + if (buffer_read(buf) != 'i') fail("expected 'i'"); + buffer_close(buf); +} + +static void test_buffer_string_eof(void) { + Buffer* buf = buffer_open_string(""); + if (buffer_read(buf) != (char)-1) fail("expected -1 on empty string"); + buffer_close(buf); +} + +static void test_buffer_string_eof_after_content(void) { + Buffer* buf = buffer_open_string("a"); + buffer_read(buf); + if (buffer_read(buf) != (char)-1) fail("expected -1 after end of string"); + buffer_close(buf); +} + +static void test_buffer_file_reads_chars(void) { + Buffer* buf = buffer_open_file("v0/tests/test_buffer.txt"); + if (buf == NULL) fail("could not open file"); + if (buffer_read(buf) != 'a') fail("expected 'a'"); + if (buffer_read(buf) != 'b') fail("expected 'b'"); + if (buffer_read(buf) != 'c') fail("expected 'c'"); + if (buffer_read(buf) != '\n') fail("expected newline after content"); + if (buffer_read(buf) != (char)-1) fail("expected -1 after file"); + buffer_close(buf); +} + +static void test_buffer_file_open_fail(void) { + Buffer* buf = buffer_open_file("v0/tests/does_not_exist.txt"); + if (buf != NULL) fail("expected NULL for non-existent file"); +} diff --git a/v0/tests/test_buffer.txt b/v0/tests/test_buffer.txt new file mode 100644 index 0000000..8baef1b --- /dev/null +++ b/v0/tests/test_buffer.txt @@ -0,0 +1 @@ +abc