Compare commits
	
		
			6 Commits
		
	
	
		
			d7a6f39068
			...
			ff1927a5c6
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| ff1927a5c6 | |||
| 7223c31154 | |||
| 8025f7f8e8 | |||
| 41867694e2 | |||
| 7596e54191 | |||
| c43aab3a2d | 
| @@ -3,10 +3,14 @@ | ||||
|  | ||||
| <label> ::= <identifier> <colon> | ||||
|  | ||||
| <directive> ::= <dot> <section_directive> | ||||
| <directive> ::= <dot> (<section_directive> | <export_directive> | <import_directive> ) | ||||
|  | ||||
| <section_directive> ::= "section" <identifier> | ||||
|  | ||||
| <export_directive> ::= "export" <identifier> | ||||
|  | ||||
| <import_directive> ::= "import" <identifier> | ||||
|  | ||||
| <instruction> ::= <identifier> <operands> | ||||
|  | ||||
| <operands> ::= <operand> ( <comma> <operand> )* | ||||
|   | ||||
| @@ -123,6 +123,10 @@ const char *ast_node_id_to_cstr(node_id_t id) { | ||||
|         return "NODE_PLUS_OR_MINUS"; | ||||
|     case NODE_SECTION_DIRECTIVE: | ||||
|         return "NODE_SECTION_DIRECTIVE"; | ||||
|     case NODE_IMPORT_DIRECTIVE: | ||||
|         return "NODE_IMPORT_DIRECTIVE"; | ||||
|     case NODE_EXPORT_DIRECTIVE: | ||||
|         return "NODE_EXPORT_DIRECTIVE"; | ||||
|     case NODE_REGISTER: | ||||
|         return "NODE_REGISTER"; | ||||
|     case NODE_SECTION: | ||||
| @@ -157,6 +161,10 @@ const char *ast_node_id_to_cstr(node_id_t id) { | ||||
|         return "NODE_ASTERISK"; | ||||
|     case NODE_DOT: | ||||
|         return "NODE_DOT"; | ||||
|     case NODE_IMPORT: | ||||
|         return "NODE_IMPORT"; | ||||
|     case NODE_EXPORT: | ||||
|         return "NODE_EXPORT"; | ||||
|     } | ||||
|     assert(!"Unreachable, weird node id" && id); | ||||
|     __builtin_unreachable(); | ||||
|   | ||||
| @@ -29,10 +29,14 @@ typedef enum node_id { | ||||
|     NODE_REGISTER_OFFSET, | ||||
|     NODE_PLUS_OR_MINUS, | ||||
|     NODE_SECTION_DIRECTIVE, | ||||
|     NODE_IMPORT_DIRECTIVE, | ||||
|     NODE_EXPORT_DIRECTIVE, | ||||
|  | ||||
|     // Validated primitives | ||||
|     NODE_REGISTER, | ||||
|     NODE_SECTION, | ||||
|     NODE_IMPORT, | ||||
|     NODE_EXPORT, | ||||
|  | ||||
|     // Primitive nodes | ||||
|     NODE_IDENTIFIER, | ||||
|   | ||||
							
								
								
									
										159
									
								
								src/encoder/symbols.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										159
									
								
								src/encoder/symbols.c
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,159 @@ | ||||
| #include "symbols.h" | ||||
| #include "../error.h" | ||||
| #include <assert.h> | ||||
| #include <stdlib.h> | ||||
| #include <string.h> | ||||
|  | ||||
| constexpr size_t symbol_table_default_cap = 64; | ||||
| constexpr size_t symbol_table_max_cap = 1 << 16; | ||||
|  | ||||
| error_t *const err_symbol_table_invalid_node = &(error_t){ | ||||
|     .message = "Unexpected node id when adding symbol to symbol table"}; | ||||
| error_t *const err_symbol_table_max_cap = &(error_t){ | ||||
|     .message = "Failed to increase symbol table length, max capacity reached"}; | ||||
| error_t *const err_symbol_table_incompatible_symbols = | ||||
|     &(error_t){.message = "Failed to update symbol with incompatible kind"}; | ||||
|  | ||||
| error_t *symbol_table_alloc(symbol_table_t **output) { | ||||
|     *output = nullptr; | ||||
|  | ||||
|     symbol_table_t *table = calloc(1, sizeof(symbol_table_t)); | ||||
|     if (table == nullptr) | ||||
|         return err_allocation_failed; | ||||
|  | ||||
|     table->symbols = calloc(symbol_table_default_cap, sizeof(symbol_t)); | ||||
|     if (table->symbols == nullptr) { | ||||
|         free(table); | ||||
|         return err_allocation_failed; | ||||
|     } | ||||
|  | ||||
|     table->cap = symbol_table_default_cap; | ||||
|     table->len = 0; | ||||
|  | ||||
|     *output = table; | ||||
|     return nullptr; | ||||
| } | ||||
|  | ||||
| void symbol_table_free(symbol_table_t *table) { | ||||
|     free(table->symbols); | ||||
|     free(table); | ||||
| } | ||||
|  | ||||
| error_t *symbol_table_grow_cap(symbol_table_t *table) { | ||||
|     if (table->cap >= symbol_table_max_cap) | ||||
|         return err_symbol_table_max_cap; | ||||
|  | ||||
|     size_t new_cap = table->cap * 2; | ||||
|     symbol_t *new_symbols = realloc(table->symbols, new_cap * sizeof(symbol_t)); | ||||
|     if (new_symbols == nullptr) | ||||
|         return err_allocation_failed; | ||||
|  | ||||
|     table->symbols = new_symbols; | ||||
|     table->cap = new_cap; | ||||
|  | ||||
|     return nullptr; | ||||
| } | ||||
|  | ||||
| error_t *symbol_table_get_node_info(ast_node_t *node, symbol_kind_t *kind, | ||||
|                                     char **name) { | ||||
|     switch (node->id) { | ||||
|     case NODE_LABEL: | ||||
|         *kind = SYMBOL_LOCAL; | ||||
|         *name = node->children[0]->token_entry->token.value; | ||||
|         return nullptr; | ||||
|     case NODE_LABEL_REFERENCE: | ||||
|         *kind = SYMBOL_REFERENCE; | ||||
|         *name = node->token_entry->token.value; | ||||
|         return nullptr; | ||||
|     case NODE_IMPORT_DIRECTIVE: | ||||
|         *kind = SYMBOL_IMPORT; | ||||
|         *name = node->children[1]->token_entry->token.value; | ||||
|         return nullptr; | ||||
|     case NODE_EXPORT_DIRECTIVE: | ||||
|         *kind = SYMBOL_EXPORT; | ||||
|         *name = node->children[1]->token_entry->token.value; | ||||
|         return nullptr; | ||||
|     default: | ||||
|         return err_symbol_table_invalid_node; | ||||
|     } | ||||
|     __builtin_unreachable(); | ||||
| } | ||||
|  | ||||
| /* | ||||
| old  \  new  | REFERENCE | LOCAL    | IMPORT   | EXPORT   | | ||||
| -------------|-----------|----------|----------|----------| | ||||
| REFERENCE    |           | replace  | replace  | replace  | | ||||
| -------------|-----------|----------|----------|----------| | ||||
| LOCAL        |           |          |   ERR    | replace  | | ||||
| -------------|-----------|----------|----------|----------| | ||||
| IMPORT       |           |          |          |   ERR    | | ||||
| -------------|-----------|----------|----------|----------| | ||||
| EXPORT       |           |          |   ERR    |          | | ||||
| -------------|-----------|----------|----------|----------| | ||||
| */ | ||||
|  | ||||
| bool symbol_table_should_update(symbol_kind_t old, symbol_kind_t new) { | ||||
|     if (old == SYMBOL_REFERENCE) | ||||
|         return new != SYMBOL_REFERENCE; | ||||
|     if (old == SYMBOL_LOCAL) | ||||
|         return new == SYMBOL_EXPORT; | ||||
|     return false; | ||||
| } | ||||
|  | ||||
| bool symbol_table_should_error(symbol_kind_t old, symbol_kind_t new) { | ||||
|     if (new == SYMBOL_IMPORT) | ||||
|         return old == SYMBOL_LOCAL || old == SYMBOL_EXPORT; | ||||
|     if (new == SYMBOL_EXPORT) | ||||
|         return old == SYMBOL_IMPORT; | ||||
|     return false; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * @pre The symbol _must not_ already be in the table. | ||||
|  */ | ||||
| error_t *symbol_table_add(symbol_table_t *table, char *name, symbol_kind_t kind, | ||||
|                           ast_node_t *node) { | ||||
|     if (table->len >= table->cap) { | ||||
|         error_t *err = symbol_table_grow_cap(table); | ||||
|         if (err) | ||||
|             return err; | ||||
|     } | ||||
|  | ||||
|     table->symbols[table->len] = (symbol_t){ | ||||
|         .name = name, | ||||
|         .kind = kind, | ||||
|         .node = node, | ||||
|     }; | ||||
|  | ||||
|     table->len += 1; | ||||
|  | ||||
|     return nullptr; | ||||
| } | ||||
|  | ||||
| error_t *symbol_table_update(symbol_table_t *table, ast_node_t *node) { | ||||
|     char *name; | ||||
|     symbol_kind_t kind; | ||||
|     error_t *err = symbol_table_get_node_info(node, &kind, &name); | ||||
|     if (err) | ||||
|         return err; | ||||
|  | ||||
|     symbol_t *symbol = symbol_table_lookup(table, name); | ||||
|     if (!symbol) | ||||
|         return symbol_table_add(table, name, kind, node); | ||||
|     if (symbol_table_should_error(symbol->kind, kind)) | ||||
|         return err_symbol_table_incompatible_symbols; | ||||
|     if (symbol_table_should_update(symbol->kind, kind)) { | ||||
|         symbol->name = name; | ||||
|         symbol->kind = kind; | ||||
|         symbol->node = node; | ||||
|     } | ||||
|     return nullptr; | ||||
| } | ||||
|  | ||||
| symbol_t *symbol_table_lookup(symbol_table_t *table, const char *name) { | ||||
|     for (size_t i = 0; i < table->len; ++i) { | ||||
|         if (strcmp(table->symbols[i].name, name) == 0) | ||||
|             return &table->symbols[i]; | ||||
|     } | ||||
|     return nullptr; | ||||
| } | ||||
							
								
								
									
										46
									
								
								src/encoder/symbols.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								src/encoder/symbols.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,46 @@ | ||||
| #ifndef INCLUDE_ENCODER_SYMBOLS_H_ | ||||
| #define INCLUDE_ENCODER_SYMBOLS_H_ | ||||
|  | ||||
| #include "../ast.h" | ||||
|  | ||||
| extern error_t *const err_symbol_table_invalid_node; | ||||
| extern error_t *const err_symbol_table_max_cap; | ||||
| extern error_t *const err_symbol_table_incompatible_symbols; | ||||
|  | ||||
| typedef enum symbol_kind { | ||||
|     SYMBOL_REFERENCE, | ||||
|     SYMBOL_LOCAL, | ||||
|     SYMBOL_EXPORT, | ||||
|     SYMBOL_IMPORT, | ||||
| } symbol_kind_t; | ||||
|  | ||||
| /** | ||||
|  * Represent a symbol in the program | ||||
|  * | ||||
|  * Symbols with the same name can only be in the table once. IMPORT or EXPORT | ||||
|  * symbols take precedence over REFERENCE symbols. If any reference symbols | ||||
|  * remain after the first encoding pass this indicates an error. Trying to add | ||||
|  * an IMPORT or EXPORT symbol if the same name already exists as the other kind | ||||
|  * is an error. | ||||
|  * | ||||
|  * This symbol table never taken ownership of the name string, it's lifted | ||||
|  * straight from the node->token.value. | ||||
|  */ | ||||
| typedef struct symbol { | ||||
|     char *name; | ||||
|     symbol_kind_t kind; | ||||
|     ast_node_t *node; | ||||
| } symbol_t; | ||||
|  | ||||
| typedef struct symbol_table { | ||||
|     size_t cap; | ||||
|     size_t len; | ||||
|     symbol_t *symbols; | ||||
| } symbol_table_t; | ||||
|  | ||||
| error_t *symbol_table_alloc(symbol_table_t **table); | ||||
| void symbol_table_free(symbol_table_t *table); | ||||
| error_t *symbol_table_update(symbol_table_t *table, ast_node_t *node); | ||||
| symbol_t *symbol_table_lookup(symbol_table_t *table, const char *name); | ||||
|  | ||||
| #endif // INCLUDE_ENCODER_SYMBOLS_H_ | ||||
							
								
								
									
										43
									
								
								src/main.c
									
									
									
									
									
								
							
							
						
						
									
										43
									
								
								src/main.c
									
									
									
									
									
								
							| @@ -32,21 +32,22 @@ void print_text(tokenlist_t *list) { | ||||
|     } | ||||
| } | ||||
|  | ||||
| void print_ast(tokenlist_t *list) { | ||||
| error_t *print_ast(tokenlist_t *list) { | ||||
|     parse_result_t result = parse(list->head); | ||||
|     if (result.err) { | ||||
|         puts(result.err->message); | ||||
|         error_free(result.err); | ||||
|         return; | ||||
|     } | ||||
|     if (result.err) | ||||
|         return result.err; | ||||
|  | ||||
|     ast_node_print(result.node); | ||||
|  | ||||
|     if (result.next != nullptr) { | ||||
|         puts("First unparsed token:"); | ||||
|         lexer_token_print(&result.next->token); | ||||
|     } | ||||
|  | ||||
|     ast_node_free(result.node); | ||||
|     if (result.next != nullptr) { | ||||
|         return errorf("did not parse entire input token stream"); | ||||
|     } | ||||
|     return nullptr; | ||||
| } | ||||
|  | ||||
| int get_execution_mode(int argc, char *argv[]) { | ||||
| @@ -63,6 +64,20 @@ int get_execution_mode(int argc, char *argv[]) { | ||||
|     return MODE_AST; | ||||
| } | ||||
|  | ||||
| error_t *do_action(mode_t mode, tokenlist_t *list) { | ||||
|     switch (mode) { | ||||
|     case MODE_TOKENS: | ||||
|         print_tokens(list); | ||||
|         return nullptr; | ||||
|     case MODE_TEXT: | ||||
|         print_text(list); | ||||
|         return nullptr; | ||||
|     case MODE_AST: | ||||
|         return print_ast(list); | ||||
|     } | ||||
|     __builtin_unreachable(); | ||||
| } | ||||
|  | ||||
| int main(int argc, char *argv[]) { | ||||
|     mode_t mode = get_execution_mode(argc, argv); | ||||
|     char *filename = argv[2]; | ||||
| @@ -81,17 +96,9 @@ int main(int argc, char *argv[]) { | ||||
|     if (err) | ||||
|         goto cleanup_tokens; | ||||
|  | ||||
|     switch (mode) { | ||||
|     case MODE_TOKENS: | ||||
|         print_tokens(list); | ||||
|         break; | ||||
|     case MODE_TEXT: | ||||
|         print_text(list); | ||||
|         break; | ||||
|     case MODE_AST: | ||||
|         print_ast(list); | ||||
|         break; | ||||
|     } | ||||
|     err = do_action(mode, list); | ||||
|     if (err) | ||||
|         goto cleanup_tokens; | ||||
|  | ||||
|     tokenlist_free(list); | ||||
|     error_free(err); | ||||
|   | ||||
| @@ -83,7 +83,7 @@ parse_result_t parse_register_expression(tokenlist_entry_t *current) { | ||||
| } | ||||
|  | ||||
| parse_result_t parse_immediate(tokenlist_entry_t *current) { | ||||
|     parser_t parsers[] = {parse_number, parse_identifier, nullptr}; | ||||
|     parser_t parsers[] = {parse_number, parse_label_reference, nullptr}; | ||||
|     parse_result_t result = parse_any(current, parsers); | ||||
|     return parse_result_wrap(NODE_IMMEDIATE, result); | ||||
| } | ||||
| @@ -119,8 +119,24 @@ parse_result_t parse_section_directive(tokenlist_entry_t *current) { | ||||
|     return parse_consecutive(current, NODE_SECTION_DIRECTIVE, parsers); | ||||
| } | ||||
|  | ||||
| parse_result_t parse_import_directive(tokenlist_entry_t *current) { | ||||
|     parser_t parsers[] = {parse_import, parse_identifier, nullptr}; | ||||
|     return parse_consecutive(current, NODE_IMPORT_DIRECTIVE, parsers); | ||||
| } | ||||
|  | ||||
| parse_result_t parse_export_directive(tokenlist_entry_t *current) { | ||||
|     parser_t parsers[] = {parse_export, parse_identifier, nullptr}; | ||||
|     return parse_consecutive(current, NODE_EXPORT_DIRECTIVE, parsers); | ||||
| } | ||||
|  | ||||
| parse_result_t parse_directive_options(tokenlist_entry_t *current) { | ||||
|     parser_t parsers[] = {parse_section_directive, parse_import_directive, | ||||
|                           parse_export_directive, nullptr}; | ||||
|     return parse_any(current, parsers); | ||||
| } | ||||
|  | ||||
| parse_result_t parse_directive(tokenlist_entry_t *current) { | ||||
|     parser_t parsers[] = {parse_dot, parse_section_directive, nullptr}; | ||||
|     parser_t parsers[] = {parse_dot, parse_directive_options, nullptr}; | ||||
|     return parse_consecutive(current, NODE_DIRECTIVE, parsers); | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -101,3 +101,19 @@ parse_result_t parse_section(tokenlist_entry_t *current) { | ||||
|     return parse_token(current, TOKEN_IDENTIFIER, NODE_SECTION, | ||||
|                        is_section_token); | ||||
| } | ||||
|  | ||||
| bool is_import_token(lexer_token_t *token) { | ||||
|     return strcmp(token->value, "import") == 0; | ||||
| } | ||||
|  | ||||
| parse_result_t parse_import(tokenlist_entry_t *current) { | ||||
|     return parse_token(current, TOKEN_IDENTIFIER, NODE_IMPORT, is_import_token); | ||||
| } | ||||
|  | ||||
| bool is_export_token(lexer_token_t *token) { | ||||
|     return strcmp(token->value, "export") == 0; | ||||
| } | ||||
|  | ||||
| parse_result_t parse_export(tokenlist_entry_t *current) { | ||||
|     return parse_token(current, TOKEN_IDENTIFIER, NODE_EXPORT, is_export_token); | ||||
| } | ||||
|   | ||||
| @@ -26,5 +26,7 @@ parse_result_t parse_label_reference(tokenlist_entry_t *current); | ||||
|  */ | ||||
| parse_result_t parse_register(tokenlist_entry_t *current); | ||||
| parse_result_t parse_section(tokenlist_entry_t *current); | ||||
| parse_result_t parse_import(tokenlist_entry_t *current); | ||||
| parse_result_t parse_export(tokenlist_entry_t *current); | ||||
|  | ||||
| #endif // INCLUDE_PARSER_PRIMITIVES_H_ | ||||
|   | ||||
							
								
								
									
										65
									
								
								tests/input/manysymbols.asm
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								tests/input/manysymbols.asm
									
									
									
									
									
										Normal file
									
								
							| @@ -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: | ||||
							
								
								
									
										12
									
								
								tests/input/symbols.asm
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								tests/input/symbols.asm
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||
| @@ -2,6 +2,9 @@ | ||||
|  | ||||
| ; Small valid code snippet that should contain all different AST nodes | ||||
|  | ||||
| .export _start | ||||
| .import exit | ||||
|  | ||||
| _start: | ||||
|     mov eax, ebx | ||||
|     lea eax, [eax + ebx * 4 + 8] | ||||
| @@ -19,3 +22,5 @@ _start: | ||||
|     push 0xffff:64 | ||||
|     push 0o777:16 | ||||
|     push 0b0001:16 | ||||
|     mov rax, 0 | ||||
|     call exit | ||||
|   | ||||
| @@ -2,12 +2,14 @@ | ||||
|  | ||||
| extern MunitTest ast_tests[]; | ||||
| extern MunitTest lexer_tests[]; | ||||
| extern MunitTest symbols_tests[]; | ||||
|  | ||||
| int main(int argc, char *argv[MUNIT_ARRAY_PARAM(argc + 1)]) { | ||||
|     MunitSuite suites[] = { | ||||
|         {"/ast",   ast_tests,   nullptr, 1, MUNIT_SUITE_OPTION_NONE}, | ||||
|         {"/lexer", lexer_tests, nullptr, 1, MUNIT_SUITE_OPTION_NONE}, | ||||
|         {nullptr,  nullptr,     nullptr, 0, 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}, | ||||
|     }; | ||||
|  | ||||
|     MunitSuite master_suite = {"/oas", nullptr, suites, 1, MUNIT_SUITE_OPTION_NONE}; | ||||
|   | ||||
							
								
								
									
										351
									
								
								tests/symbols.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										351
									
								
								tests/symbols.c
									
									
									
									
									
										Normal file
									
								
							| @@ -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 <string.h> | ||||
|  | ||||
| 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} | ||||
| }; | ||||
		Reference in New Issue
	
	Block a user