diff --git a/v0/ast.h b/v0/ast.h index 846ce64..26b6ca2 100644 --- a/v0/ast.h +++ b/v0/ast.h @@ -4,6 +4,13 @@ #ifndef AST_H #define AST_H +#include + +typedef struct { + /// @brief The name of the module being imported. + char* module_name; +} ImportDeclaration; + /** * The top-level model. * Every file matches an entire Module. @@ -11,6 +18,12 @@ typedef struct { /// @brief The name of the module. char* name; + + /// @brief The list of imports in the module. + ImportDeclaration* imports; + + /// @brief The number of imports in the module. + size_t import_count; } Module; #endif diff --git a/v0/parser.c b/v0/parser.c index e991c96..3887d20 100644 --- a/v0/parser.c +++ b/v0/parser.c @@ -32,11 +32,55 @@ Module* parser_parse(TokenStream* ts) { return NULL; } + module->imports = NULL; + module->import_count = 0; + + while (1) { + t = tokenstream_next(ts); + if (t.token != TOKEN_IMPORT) { + break; + } + + ImportDeclaration* new_imports = realloc(module->imports, (module->import_count + 1) * sizeof(ImportDeclaration)); + if (!new_imports) { + parser_free(module); + return NULL; + } + module->imports = new_imports; + + t = tokenstream_next(ts); + if (t.token != TOKEN_IDENTIFIER) { + parser_free(module); + return NULL; + } + + module->imports[module->import_count].module_name = (char*)malloc(t.text.length + 1); + if (!module->imports[module->import_count].module_name) { + parser_free(module); + return NULL; + } + memcpy(module->imports[module->import_count].module_name, t.text.data, t.text.length); + module->imports[module->import_count].module_name[t.text.length] = '\0'; + module->import_count++; + + t = tokenstream_next(ts); + if (t.token != TOKEN_SEMICOLON) { + parser_free(module); + return NULL; + } + } + return module; } void parser_free(Module* module) { if (module == NULL) return; + if (module->imports != NULL) { + for (size_t i = 0; i < module->import_count; i++) { + free(module->imports[i].module_name); + } + free(module->imports); + } free(module->name); free(module); } diff --git a/v0/test.c b/v0/test.c index 120210e..f90f33e 100644 --- a/v0/test.c +++ b/v0/test.c @@ -67,6 +67,14 @@ void assert_log_file(const char* filepath, const char* msg) { free(content); } +void assert_int(int expected, int actual, const char* msg) { + if (expected != actual) { + char buf[64]; + snprintf(buf, sizeof(buf), "%s (expected %d, got %d)", msg, expected, actual); + fail(buf); + } +} + static void log_append(const char* msg) { size_t oldLen = s_logOutput ? strlen(s_logOutput) : 0; size_t newLen = oldLen + strlen(msg) + 1; @@ -111,6 +119,7 @@ static TestCase s_tests[] = { {"tokenstream_unknown_token", test_tokenstream_unknown_token}, {"tokenstream_info", test_tokenstream_info}, {"parser_module_name", test_parser_module_name}, + {"parser_imports", test_parser_imports}, {"log_error", test_log_error}, {"log_on_line", test_log_on_line}, }; diff --git a/v0/test.h b/v0/test.h index 2fee8fd..510d1b9 100644 --- a/v0/test.h +++ b/v0/test.h @@ -44,4 +44,9 @@ void assert_log(const char* expected, const char* msg); */ void assert_log_file(const char* filepath, const char* msg); +/** + * Asserts that two integers are equal. + */ +void assert_int(int expected, int actual, const char* msg); + #endif diff --git a/v0/test_parser.c b/v0/test_parser.c index 08dccb8..2d8d428 100644 --- a/v0/test_parser.c +++ b/v0/test_parser.c @@ -12,3 +12,28 @@ static void test_parser_module_name(void) { parser_free(m); tokenstream_close(ts); } + +static void test_parser_bad_module_name(void) { + TokenStream* ts = tokenstream_open("test.c", "import other_module;"); + Module* m = parser_parse(ts); + + assert_log_file("bad_module_name.log", "expected error to be logged for bad module name"); + + parser_free(m); + tokenstream_close(ts); +} + +static void test_parser_imports(void) { + TokenStream* ts = tokenstream_open("test.c", "module my_module; import other_module;"); + Module* m = parser_parse(ts); + + assert_not_null(m, "expected module to be parsed"); + assert_str("my_module", m->name, "expected name 'my_module'"); + + assert_not_null(m->imports, "expected imports to be parsed"); + assert_int(1, m->import_count, "expected one import"); + assert_str("other_module", m->imports[0].module_name, "expected import name 'other_module'"); + + parser_free(m); + tokenstream_close(ts); +} \ No newline at end of file diff --git a/v0/tests/bad_module_log.txt b/v0/tests/bad_module_log.txt new file mode 100644 index 0000000..afced76 --- /dev/null +++ b/v0/tests/bad_module_log.txt @@ -0,0 +1,4 @@ +--- test.c --- +1| import other_module.c + ^^^^^^ + expected `module`