diff --git a/tests/input/manysymbols.asm b/tests/input/manysymbols.asm new file mode 100644 index 0000000..76a0ba0 --- /dev/null +++ b/tests/input/manysymbols.asm @@ -0,0 +1,65 @@ +lbl_0: ; 65 symbols used for testing growing the symbols table +lbl_1: +lbl_2: +lbl_3: +lbl_4: +lbl_5: +lbl_6: +lbl_7: +lbl_8: +lbl_9: +lbl_10: +lbl_11: +lbl_12: +lbl_13: +lbl_14: +lbl_15: +lbl_16: +lbl_17: +lbl_18: +lbl_19: +lbl_20: +lbl_21: +lbl_22: +lbl_23: +lbl_24: +lbl_25: +lbl_26: +lbl_27: +lbl_28: +lbl_29: +lbl_30: +lbl_31: +lbl_32: +lbl_33: +lbl_34: +lbl_35: +lbl_36: +lbl_37: +lbl_38: +lbl_39: +lbl_40: +lbl_41: +lbl_42: +lbl_43: +lbl_44: +lbl_45: +lbl_46: +lbl_47: +lbl_48: +lbl_49: +lbl_50: +lbl_51: +lbl_52: +lbl_53: +lbl_54: +lbl_55: +lbl_56: +lbl_57: +lbl_58: +lbl_59: +lbl_60: +lbl_61: +lbl_62: +lbl_63: +lbl_64: diff --git a/tests/input/symbols.asm b/tests/input/symbols.asm new file mode 100644 index 0000000..921d90a --- /dev/null +++ b/tests/input/symbols.asm @@ -0,0 +1,12 @@ +.import test +.export test +test: + call test +.import more +.export more +more: + call more +.import other +.export other +other: + call other diff --git a/tests/main.c b/tests/main.c index c94d54d..cf7162f 100644 --- a/tests/main.c +++ b/tests/main.c @@ -3,12 +3,14 @@ extern MunitTest ast_tests[]; extern MunitTest lexer_tests[]; extern MunitTest regression_tests[]; +extern MunitTest symbols_tests[]; int main(int argc, char *argv[MUNIT_ARRAY_PARAM(argc + 1)]) { MunitSuite suites[] = { {"/regression", regression_tests, nullptr, 1, MUNIT_SUITE_OPTION_NONE}, {"/ast", ast_tests, nullptr, 1, MUNIT_SUITE_OPTION_NONE}, {"/lexer", lexer_tests, nullptr, 1, MUNIT_SUITE_OPTION_NONE}, + {"/symbols", symbols_tests, nullptr, 1, MUNIT_SUITE_OPTION_NONE}, {nullptr, nullptr, nullptr, 0, MUNIT_SUITE_OPTION_NONE}, }; diff --git a/tests/symbols.c b/tests/symbols.c new file mode 100644 index 0000000..f844e34 --- /dev/null +++ b/tests/symbols.c @@ -0,0 +1,351 @@ +#include "../src/encoder/symbols.h" +#include "../src/ast.h" +#include "../src/error.h" +#include "../src/lexer.h" +#include "../src/parser/parser.h" +#include "munit.h" +#include + +void symbols_setup_test(ast_node_t **node, tokenlist_t **list, char *path) { + lexer_t *lex = &(lexer_t){}; + lexer_open(lex, path); + tokenlist_alloc(list); + tokenlist_fill(*list, lex); + parse_result_t result = parse((*list)->head); + lexer_close(lex); + + *node = result.node; +} + +MunitResult test_symbol_table_alloc(const MunitParameter params[], void *data) { + (void)params; + (void)data; + + symbol_table_t *table = nullptr; + error_t *err = symbol_table_alloc(&table); + + munit_assert_ptr_not_null(table); + munit_assert_ptr_null(err); + munit_assert_size(table->cap, ==, 64); // Default capacity + munit_assert_size(table->len, ==, 0); + munit_assert_ptr_not_null(table->symbols); + + symbol_table_free(table); + return MUNIT_OK; +} + +MunitResult test_symbol_table_lookup_empty(const MunitParameter params[], void *data) { + (void)params; + (void)data; + + symbol_table_t *table = nullptr; + symbol_table_alloc(&table); + + symbol_t *symbol = symbol_table_lookup(table, "nonexistent"); + munit_assert_ptr_null(symbol); + + symbol_table_free(table); + return MUNIT_OK; +} + +MunitResult test_symbol_add_reference(const MunitParameter params[], void *data) { + (void)params; + (void)data; + ast_node_t *root; + tokenlist_t *list; + symbol_table_t *table = nullptr; + symbols_setup_test(&root, &list, "tests/input/symbols.asm"); + symbol_table_alloc(&table); + + ast_node_t *reference = root->children[3]->children[1]->children[0]->children[0]; + munit_assert_int(reference->id, ==, NODE_LABEL_REFERENCE); + munit_assert_size(table->len, ==, 0); + + error_t *err = symbol_table_update(table, reference); + munit_assert_null(err); + munit_assert_size(table->len, ==, 1); + + symbol_t *symbol = symbol_table_lookup(table, "test"); + munit_assert_not_null(symbol); + munit_assert_int(SYMBOL_REFERENCE, ==, symbol->kind); + munit_assert_ptr_equal(reference, symbol->node); + munit_assert_string_equal(symbol->name, "test"); + + symbol_table_free(table); + ast_node_free(root); + tokenlist_free(list); + return MUNIT_OK; +} + +MunitResult test_symbol_add_label(const MunitParameter params[], void *data) { + (void)params; + (void)data; + ast_node_t *root; + tokenlist_t *list; + symbol_table_t *table = nullptr; + symbols_setup_test(&root, &list, "tests/input/symbols.asm"); + symbol_table_alloc(&table); + + ast_node_t *label = root->children[2]; + munit_assert_int(label->id, ==, NODE_LABEL); + munit_assert_size(table->len, ==, 0); + + error_t *err = symbol_table_update(table, label); + munit_assert_null(err); + munit_assert_size(table->len, ==, 1); + + symbol_t *symbol = symbol_table_lookup(table, "test"); + munit_assert_not_null(symbol); + munit_assert_int(SYMBOL_LOCAL, ==, symbol->kind); + munit_assert_ptr_equal(label, symbol->node); + munit_assert_string_equal(symbol->name, "test"); + + symbol_table_free(table); + ast_node_free(root); + tokenlist_free(list); + return MUNIT_OK; +} + +MunitResult test_symbol_add_import(const MunitParameter params[], void *data) { + (void)params; + (void)data; + ast_node_t *root; + tokenlist_t *list; + symbol_table_t *table = nullptr; + symbols_setup_test(&root, &list, "tests/input/symbols.asm"); + symbol_table_alloc(&table); + + ast_node_t *import_directive = root->children[0]->children[1]; + munit_assert_int(import_directive->id, ==, NODE_IMPORT_DIRECTIVE); + munit_assert_size(table->len, ==, 0); + + error_t *err = symbol_table_update(table, import_directive); + munit_assert_null(err); + munit_assert_size(table->len, ==, 1); + + symbol_t *symbol = symbol_table_lookup(table, "test"); + munit_assert_not_null(symbol); + munit_assert_int(SYMBOL_IMPORT, ==, symbol->kind); + munit_assert_ptr_equal(import_directive, symbol->node); + munit_assert_string_equal(symbol->name, "test"); + + symbol_table_free(table); + ast_node_free(root); + tokenlist_free(list); + return MUNIT_OK; +} + +void test_symbol_update(const char *name, ast_node_t *first, symbol_kind_t first_kind, ast_node_t *second, + symbol_kind_t second_kind, bool should_succeed, bool should_update) { + symbol_table_t *table = nullptr; + symbol_table_alloc(&table); + + munit_assert_size(table->len, ==, 0); + error_t *err = symbol_table_update(table, first); + munit_assert_null(err); + munit_assert_size(table->len, ==, 1); + + symbol_t *symbol = symbol_table_lookup(table, name); + munit_assert_not_null(symbol); + munit_assert_int(first_kind, ==, symbol->kind); + munit_assert_ptr_equal(first, symbol->node); + munit_assert_string_equal(symbol->name, name); + + err = symbol_table_update(table, second); + if (should_succeed) + munit_assert_null(err); + else + munit_assert_ptr_equal(err, err_symbol_table_incompatible_symbols); + munit_assert_size(table->len, ==, 1); + + symbol = symbol_table_lookup(table, name); + if (should_update) { + munit_assert_not_null(symbol); + munit_assert_int(second_kind, ==, symbol->kind); + munit_assert_ptr_equal(second, symbol->node); + munit_assert_string_equal(symbol->name, name); + } else { + munit_assert_not_null(symbol); + munit_assert_int(first_kind, ==, symbol->kind); + munit_assert_ptr_equal(first, symbol->node); + munit_assert_string_equal(symbol->name, name); + } + + symbol_table_free(table); +} + +MunitResult test_symbol_upgrade_valid(const MunitParameter params[], void *data) { + ast_node_t *root; + tokenlist_t *list; + + symbols_setup_test(&root, &list, "tests/input/symbols.asm"); + + ast_node_t *reference = root->children[3]->children[1]->children[0]->children[0]; + ast_node_t *label = root->children[2]; + ast_node_t *import_directive = root->children[0]->children[1]; + ast_node_t *export_directive = root->children[1]->children[1]; + + // real upgrades + test_symbol_update("test", reference, SYMBOL_REFERENCE, label, SYMBOL_LOCAL, true, true); + test_symbol_update("test", reference, SYMBOL_REFERENCE, import_directive, SYMBOL_IMPORT, true, true); + test_symbol_update("test", reference, SYMBOL_REFERENCE, export_directive, SYMBOL_EXPORT, true, true); + test_symbol_update("test", label, SYMBOL_LOCAL, export_directive, SYMBOL_EXPORT, true, true); + + // identity upgrades + test_symbol_update("test", reference, SYMBOL_REFERENCE, reference, SYMBOL_REFERENCE, true, false); + test_symbol_update("test", label, SYMBOL_LOCAL, label, SYMBOL_LOCAL, true, false); + test_symbol_update("test", import_directive, SYMBOL_IMPORT, import_directive, SYMBOL_IMPORT, true, false); + test_symbol_update("test", export_directive, SYMBOL_EXPORT, export_directive, SYMBOL_EXPORT, true, false); + + // downgrades that are allowed and ignored + test_symbol_update("test", label, SYMBOL_LOCAL, reference, SYMBOL_REFERENCE, true, false); + test_symbol_update("test", import_directive, SYMBOL_IMPORT, reference, SYMBOL_REFERENCE, true, false); + test_symbol_update("test", export_directive, SYMBOL_EXPORT, reference, SYMBOL_REFERENCE, true, false); + test_symbol_update("test", export_directive, SYMBOL_EXPORT, label, SYMBOL_LOCAL, true, false); + test_symbol_update("test", import_directive, SYMBOL_IMPORT, label, SYMBOL_LOCAL, true, false); + + ast_node_free(root); + tokenlist_free(list); + return MUNIT_OK; +} + +MunitResult test_symbol_upgrade_invalid(const MunitParameter params[], void *data) { + ast_node_t *root; + tokenlist_t *list; + + symbols_setup_test(&root, &list, "tests/input/symbols.asm"); + + ast_node_t *reference = root->children[3]->children[1]->children[0]->children[0]; + ast_node_t *label = root->children[2]; + ast_node_t *import_directive = root->children[0]->children[1]; + ast_node_t *export_directive = root->children[1]->children[1]; + + // invalid upgrades + test_symbol_update("test", label, SYMBOL_LOCAL, import_directive, SYMBOL_IMPORT, false, false); + test_symbol_update("test", export_directive, SYMBOL_EXPORT, import_directive, SYMBOL_IMPORT, false, false); + test_symbol_update("test", import_directive, SYMBOL_IMPORT, export_directive, SYMBOL_EXPORT, false, false); + + ast_node_free(root); + tokenlist_free(list); + return MUNIT_OK; +} + +MunitResult test_symbol_add_export(const MunitParameter params[], void *data) { + (void)params; + (void)data; + ast_node_t *root; + tokenlist_t *list; + symbol_table_t *table = nullptr; + symbols_setup_test(&root, &list, "tests/input/symbols.asm"); + symbol_table_alloc(&table); + + ast_node_t *export_directive = root->children[1]->children[1]; + munit_assert_int(export_directive->id, ==, NODE_EXPORT_DIRECTIVE); + munit_assert_size(table->len, ==, 0); + + error_t *err = symbol_table_update(table, export_directive); + munit_assert_null(err); + munit_assert_size(table->len, ==, 1); + + symbol_t *symbol = symbol_table_lookup(table, "test"); + munit_assert_not_null(symbol); + munit_assert_int(SYMBOL_EXPORT, ==, symbol->kind); + munit_assert_ptr_equal(export_directive, symbol->node); + munit_assert_string_equal(symbol->name, "test"); + + symbol_table_free(table); + ast_node_free(root); + tokenlist_free(list); + return MUNIT_OK; +} + +MunitResult test_symbol_table_growth(const MunitParameter params[], void *data) { + (void)params; + (void)data; + ast_node_t *root; + tokenlist_t *list; + symbol_table_t *table = nullptr; + + // Set up with our manysymbols.asm file + symbols_setup_test(&root, &list, "tests/input/manysymbols.asm"); + symbol_table_alloc(&table); + + // Initial capacity should be the default (64) + munit_assert_size(table->cap, ==, 64); + munit_assert_size(table->len, ==, 0); + + // Add the first 64 labels (indices 0-63) + size_t initial_cap = table->cap; + for (size_t i = 0; i < 64; i++) { + ast_node_t *label = root->children[i]; + munit_assert_int(label->id, ==, NODE_LABEL); + + error_t *err = symbol_table_update(table, label); + munit_assert_null(err); + munit_assert_size(table->len, ==, i + 1); + + // Capacity should remain the same for the first 64 labels + munit_assert_size(table->cap, ==, initial_cap); + } + + // Now add the 65th label (index 64), which should trigger growth + ast_node_t *final_label = root->children[64]; + munit_assert_int(final_label->id, ==, NODE_LABEL); + + error_t *err = symbol_table_update(table, final_label); + munit_assert_null(err); + munit_assert_size(table->len, ==, 65); + + // Capacity should have doubled + munit_assert_size(table->cap, ==, initial_cap * 2); + + // Validate we can look up all the symbols + for (size_t i = 0; i <= 64; i++) { + char name[10]; + sprintf(name, "lbl_%zu", i); + + symbol_t *symbol = symbol_table_lookup(table, name); + munit_assert_not_null(symbol); + munit_assert_int(SYMBOL_LOCAL, ==, symbol->kind); + munit_assert_string_equal(symbol->name, name); + } + + symbol_table_free(table); + ast_node_free(root); + tokenlist_free(list); + return MUNIT_OK; +} + +MunitResult test_symbol_invalid_node(const MunitParameter params[], void *data) { + (void)params; + (void)data; + ast_node_t *root; + tokenlist_t *list; + symbol_table_t *table = nullptr; + symbols_setup_test(&root, &list, "tests/input/symbols.asm"); + symbol_table_alloc(&table); + + munit_assert_size(table->len, ==, 0); + error_t *err = symbol_table_update(table, root); + munit_assert_ptr_equal(err, err_symbol_table_invalid_node); + munit_assert_size(table->len, ==, 0); + + symbol_table_free(table); + ast_node_free(root); + tokenlist_free(list); + return MUNIT_OK; +} + +MunitTest symbols_tests[] = { + {"/table_alloc", test_symbol_table_alloc, nullptr, nullptr, MUNIT_TEST_OPTION_NONE, nullptr}, + {"/table_lookup_empty", test_symbol_table_lookup_empty, nullptr, nullptr, MUNIT_TEST_OPTION_NONE, nullptr}, + {"/add_reference", test_symbol_add_reference, nullptr, nullptr, MUNIT_TEST_OPTION_NONE, nullptr}, + {"/add_label", test_symbol_add_label, nullptr, nullptr, MUNIT_TEST_OPTION_NONE, nullptr}, + {"/add_import", test_symbol_add_import, nullptr, nullptr, MUNIT_TEST_OPTION_NONE, nullptr}, + {"/add_export", test_symbol_add_export, nullptr, nullptr, MUNIT_TEST_OPTION_NONE, nullptr}, + {"/upgrade_valid", test_symbol_upgrade_valid, nullptr, nullptr, MUNIT_TEST_OPTION_NONE, nullptr}, + {"/upgrade_invalid", test_symbol_upgrade_invalid, nullptr, nullptr, MUNIT_TEST_OPTION_NONE, nullptr}, + {"/table_growth", test_symbol_table_growth, nullptr, nullptr, MUNIT_TEST_OPTION_NONE, nullptr}, + {"/invalid_node", test_symbol_invalid_node, nullptr, nullptr, MUNIT_TEST_OPTION_NONE, nullptr}, + {nullptr, nullptr, nullptr, nullptr, MUNIT_TEST_OPTION_NONE, nullptr} +};