diff options
| author | hachem <im@hachem.wtf> | 2026-08-31 19:58:56 +0200 |
|---|---|---|
| committer | hachem <im@hachem.wtf> | 2026-08-31 19:58:56 +0200 |
| commit | 5b06b0e4b8effaacd23308d6a6b26c525e7d3af4 (patch) | |
| tree | 351155777028e604845d3dfec27d3caf3b22257f | |
| parent | cbdb9073480c3e4d8a088b306c23406cf42e25c0 (diff) | |
feat: add enums and structs
| -rw-r--r-- | docs/language.md | 33 | ||||
| -rw-r--r-- | examples/records.hdass | 42 | ||||
| -rwxr-xr-x | scripts/test_examples.sh | 1 | ||||
| -rw-r--r-- | src/codegen/nasm.c | 83 | ||||
| -rw-r--r-- | src/lexer/lexer.c | 4 | ||||
| -rw-r--r-- | src/lexer/lexer.h | 2 | ||||
| -rw-r--r-- | src/parser/ast.c | 90 | ||||
| -rw-r--r-- | src/parser/ast.h | 54 | ||||
| -rw-r--r-- | src/parser/parser.c | 96 | ||||
| -rw-r--r-- | src/sema/sema.c | 76 | ||||
| -rw-r--r-- | tests/codegen_test.c | 28 | ||||
| -rw-r--r-- | tests/lexer_test.c | 8 | ||||
| -rw-r--r-- | tests/parser_test.c | 22 | ||||
| -rw-r--r-- | tests/sema_test.c | 12 |
14 files changed, 536 insertions, 15 deletions
diff --git a/docs/language.md b/docs/language.md index 43b6246..5e75c54 100644 --- a/docs/language.md +++ b/docs/language.md @@ -59,6 +59,39 @@ const AREA = 8 * 6 // 48 data message = "type shi.\n" // message -> address, message.len -> 10 ``` +## Enums and structs + +Both describe compile-time values reached with `Name.member`, which folds to an integer. + +`enum` names a set of constants numbered from 0: + +```hdass +enum Status +{ + Ok, // 0 + Warn, // 1 + Fail // 2 +} + +rax = Status.Fail // mov rax, 2 +``` + +`struct` describes a packed memory layout (no padding). Fields are `name` or `name: size`, where size defaults to `qword`. `Name.field` is the field's byte offset, and `Name.size` is the total size. + +```hdass +struct Point +{ + x // qword, offset 0 + y // qword, offset 8 + flag: byte // offset 16 +} + +rsi += Point.y // add rsi, 8 +rax = Point.size // mov rax, 17 +``` + +A struct is layout only — it allocates nothing. Pair it with a `stack` buffer and pointer arithmetic (see [examples/records.hdass](../examples/records.hdass)). + ## Procedures `proc` groups a body. Parameters name registers — `value` below is `rdi`. diff --git a/examples/records.hdass b/examples/records.hdass new file mode 100644 index 0000000..9800839 --- /dev/null +++ b/examples/records.hdass @@ -0,0 +1,42 @@ +[entry: main] + +// An enum of status codes and a struct describing a memory layout. Stores two +// fields into a stack instance, reads them back, and exits with their sum. +enum Status +{ + Ok, + Warn, + Fail +} + +struct Pair +{ + a: qword + b: qword +} + +const SYS_EXIT = 60 + +proc main +{ + stack pair[16] // Pair.size + + rsi = pair + rbx = 40 + ^rsi = rbx // pair.a (offset 0) + + rsi = pair + rsi += Pair.b + rcx = Status.Fail // 2 + ^rsi = rcx // pair.b (offset 8) + + rsi = pair + rdi = ^rsi // load a = 40 + rsi = pair + rsi += Pair.b + rax = ^rsi // load b = 2 + rdi += rax // 42 + + rax = SYS_EXIT + syscall +} diff --git a/scripts/test_examples.sh b/scripts/test_examples.sh index 3e04fde..0928f7c 100755 --- a/scripts/test_examples.sh +++ b/scripts/test_examples.sh @@ -84,6 +84,7 @@ check logical "logical register names (r1..r14)" examples/logical.hdass check mul_div "multiply and non-rax division" examples/mul_div.hdass 8 "" check load "stores then loads through a pointer" examples/load.hdass 7 "" check constants "hex literals and constant folding" examples/constants.hdass 42 "" +check records "enum values and struct field offsets" examples/records.hdass 42 "" check fibonacci "prints the first ten Fibonacci numbers" examples/fibonacci.hdass 0 "0 1 1 diff --git a/src/codegen/nasm.c b/src/codegen/nasm.c index fef68a8..9c1a178 100644 --- a/src/codegen/nasm.c +++ b/src/codegen/nasm.c @@ -209,6 +209,84 @@ static enum StoreSize size_from_int(struct Token token) } } +static bool tokens_equal(struct Token a, struct Token b) +{ + return a.length == b.length && memcmp(a.start, b.start, a.length) == 0; +} + +static bool token_matches(struct Token token, const char* text) +{ + size_t length = strlen(text); + return token.length == length && memcmp(token.start, text, length) == 0; +} + +static struct EnumDecl* find_enum(struct Program* program, struct Token name) +{ + for (size_t i = 0; i < program->enum_count; i += 1) + if (tokens_equal(program->enums[i].name, name)) + return &program->enums[i]; + + return NULL; +} + +static struct StructDecl* find_struct(struct Program* program, struct Token name) +{ + for (size_t i = 0; i < program->struct_count; i += 1) + if (tokens_equal(program->structs[i].name, name)) + return &program->structs[i]; + + return NULL; +} + +static uint64_t store_size_bytes(enum StoreSize size) +{ + switch (size) + { + case STORE_SIZE_BYTE: return 1; + case STORE_SIZE_WORD: return 2; + case STORE_SIZE_DWORD: return 4; + default: return 8; + } +} + +// an enum member folds to its 0-based index; a struct member folds to its byte +// offset (or the total size for `.size`) +static bool emit_named_member(struct Emitter* emitter, struct Token object, struct Token member) +{ + struct EnumDecl* enumeration = find_enum(emitter->program, object); + if (enumeration != NULL) + { + for (size_t i = 0; i < enumeration->member_count; i += 1) + if (tokens_equal(enumeration->members[i], member)) + { + fprintf(emitter->out, "%zu", i); + return true; + } + } + + struct StructDecl* layout = find_struct(emitter->program, object); + if (layout != NULL) + { + uint64_t offset = 0; + for (size_t i = 0; i < layout->field_count; i += 1) + { + if (tokens_equal(layout->fields[i].name, member)) + { + fprintf(emitter->out, "%llu", (unsigned long long)offset); + return true; + } + offset += store_size_bytes(layout->fields[i].size); + } + if (token_matches(member, "size")) + { + fprintf(emitter->out, "%llu", (unsigned long long)offset); + return true; + } + } + + return false; +} + static bool emit_operand(struct Emitter* emitter, struct Expr* expr) { switch (expr->kind) @@ -235,6 +313,11 @@ static bool emit_operand(struct Emitter* emitter, struct Expr* expr) return true; } + // enum value or struct offset + if (expr->member.object->kind == EXPR_PRIMARY + && emit_named_member(emitter, expr->member.object->primary.token, expr->member.member)) + return true; + if (!emit_operand(emitter, expr->member.object)) return false; fprintf(emitter->out, ".%.*s", (int)expr->member.member.length, expr->member.member.start); diff --git a/src/lexer/lexer.c b/src/lexer/lexer.c index 23d0a82..22a671c 100644 --- a/src/lexer/lexer.c +++ b/src/lexer/lexer.c @@ -34,6 +34,8 @@ static enum TokenType identifier_type(const char* start, size_t length) { "const", 5, TOKEN_CONST }, { "data", 4, TOKEN_DATA }, { "proc", 4, TOKEN_PROC }, + { "enum", 4, TOKEN_ENUM }, + { "struct", 6, TOKEN_STRUCT }, { "stack", 5, TOKEN_STACK }, { "if", 2, TOKEN_IF }, { "goto", 4, TOKEN_GOTO }, @@ -247,6 +249,8 @@ const char* token_type_name(enum TokenType type) case TOKEN_CONST: return "const"; case TOKEN_DATA: return "data"; case TOKEN_PROC: return "proc"; + case TOKEN_ENUM: return "enum"; + case TOKEN_STRUCT: return "struct"; case TOKEN_STACK: return "stack"; case TOKEN_IF: return "if"; case TOKEN_GOTO: return "goto"; diff --git a/src/lexer/lexer.h b/src/lexer/lexer.h index afe6e7f..3bc215e 100644 --- a/src/lexer/lexer.h +++ b/src/lexer/lexer.h @@ -14,6 +14,8 @@ enum TokenType TOKEN_CONST, TOKEN_DATA, TOKEN_PROC, + TOKEN_ENUM, + TOKEN_STRUCT, TOKEN_STACK, TOKEN_IF, TOKEN_GOTO, diff --git a/src/parser/ast.c b/src/parser/ast.c index 704e9a4..f8bd9cc 100644 --- a/src/parser/ast.c +++ b/src/parser/ast.c @@ -70,6 +70,12 @@ struct Program create_program(void) program.data_decls = NULL; program.data_count = 0; program.data_capacity = 0; + program.enums = NULL; + program.enum_count = 0; + program.enum_capacity = 0; + program.structs = NULL; + program.struct_count = 0; + program.struct_capacity = 0; program.procs = NULL; program.proc_count = 0; program.proc_capacity = 0; @@ -83,6 +89,14 @@ void free_program(struct Program* program) free(program->consts); free(program->data_decls); + for (size_t i = 0; i < program->enum_count; i += 1) + free(program->enums[i].members); + free(program->enums); + + for (size_t i = 0; i < program->struct_count; i += 1) + free(program->structs[i].fields); + free(program->structs); + for (size_t i = 0; i < program->proc_count; i += 1) free_proc(&program->procs[i]); free(program->procs); @@ -93,6 +107,12 @@ void free_program(struct Program* program) program->data_decls = NULL; program->data_count = 0; program->data_capacity = 0; + program->enums = NULL; + program->enum_count = 0; + program->enum_capacity = 0; + program->structs = NULL; + program->struct_count = 0; + program->struct_capacity = 0; program->procs = NULL; program->proc_count = 0; program->proc_capacity = 0; @@ -125,6 +145,76 @@ void add_data(struct Program* program, struct DataDecl decl) program->data_count += 1; } +struct EnumDecl create_enum(void) +{ + struct EnumDecl decl; + decl.members = NULL; + decl.member_count = 0; + decl.member_capacity = 0; + return decl; +} + +void add_enum_member(struct EnumDecl* decl, struct Token member) +{ + if (decl->member_count == decl->member_capacity) + { + size_t capacity = decl->member_capacity < 8 ? 8 : decl->member_capacity * 2; + decl->members = realloc(decl->members, capacity * sizeof(struct Token)); + decl->member_capacity = capacity; + } + + decl->members[decl->member_count] = member; + decl->member_count += 1; +} + +void add_enum(struct Program* program, struct EnumDecl decl) +{ + if (program->enum_count == program->enum_capacity) + { + size_t capacity = program->enum_capacity < 8 ? 8 : program->enum_capacity * 2; + program->enums = realloc(program->enums, capacity * sizeof(struct EnumDecl)); + program->enum_capacity = capacity; + } + + program->enums[program->enum_count] = decl; + program->enum_count += 1; +} + +struct StructDecl create_struct(void) +{ + struct StructDecl decl; + decl.fields = NULL; + decl.field_count = 0; + decl.field_capacity = 0; + return decl; +} + +void add_struct_field(struct StructDecl* decl, struct StructField field) +{ + if (decl->field_count == decl->field_capacity) + { + size_t capacity = decl->field_capacity < 8 ? 8 : decl->field_capacity * 2; + decl->fields = realloc(decl->fields, capacity * sizeof(struct StructField)); + decl->field_capacity = capacity; + } + + decl->fields[decl->field_count] = field; + decl->field_count += 1; +} + +void add_struct(struct Program* program, struct StructDecl decl) +{ + if (program->struct_count == program->struct_capacity) + { + size_t capacity = program->struct_capacity < 8 ? 8 : program->struct_capacity * 2; + program->structs = realloc(program->structs, capacity * sizeof(struct StructDecl)); + program->struct_capacity = capacity; + } + + program->structs[program->struct_count] = decl; + program->struct_count += 1; +} + struct ProcDecl create_proc(void) { struct ProcDecl proc; diff --git a/src/parser/ast.h b/src/parser/ast.h index 98e8dea..b114fb1 100644 --- a/src/parser/ast.h +++ b/src/parser/ast.h @@ -6,6 +6,15 @@ #include "lexer/lexer.h" +enum StoreSize +{ + STORE_SIZE_NONE, + STORE_SIZE_BYTE, + STORE_SIZE_WORD, + STORE_SIZE_DWORD, + STORE_SIZE_QWORD, +}; + struct ConstDecl { struct Token name; @@ -18,19 +27,32 @@ struct DataDecl struct Token value; }; -struct Param +struct EnumDecl { struct Token name; - struct Token reg; + struct Token* members; + size_t member_count; + size_t member_capacity; }; -enum StoreSize +struct StructField { - STORE_SIZE_NONE, - STORE_SIZE_BYTE, - STORE_SIZE_WORD, - STORE_SIZE_DWORD, - STORE_SIZE_QWORD, + struct Token name; + enum StoreSize size; +}; + +struct StructDecl +{ + struct Token name; + struct StructField* fields; + size_t field_count; + size_t field_capacity; +}; + +struct Param +{ + struct Token name; + struct Token reg; }; enum ExprKind @@ -175,6 +197,14 @@ struct Program size_t data_count; size_t data_capacity; + struct EnumDecl* enums; + size_t enum_count; + size_t enum_capacity; + + struct StructDecl* structs; + size_t struct_count; + size_t struct_capacity; + struct ProcDecl* procs; size_t proc_count; size_t proc_capacity; @@ -185,6 +215,14 @@ void free_program(struct Program* program); void add_const(struct Program* program, struct ConstDecl decl); void add_data(struct Program* program, struct DataDecl decl); +struct EnumDecl create_enum(void); +void add_enum_member(struct EnumDecl* decl, struct Token member); +void add_enum(struct Program* program, struct EnumDecl decl); + +struct StructDecl create_struct(void); +void add_struct_field(struct StructDecl* decl, struct StructField field); +void add_struct(struct Program* program, struct StructDecl decl); + struct ProcDecl create_proc(void); void free_proc(struct ProcDecl* proc); void add_param(struct ProcDecl* proc, struct Param param); diff --git a/src/parser/parser.c b/src/parser/parser.c index 87dedbd..682e193 100644 --- a/src/parser/parser.c +++ b/src/parser/parser.c @@ -53,6 +53,7 @@ static bool consume(struct Parser* parser, enum TokenType type, const char* mess } static struct Expr* parse_expression(struct Parser* parser); +static enum StoreSize parse_store_size(struct Parser* parser); static bool parse_const(struct Parser* parser, struct Program* program) { @@ -92,6 +93,89 @@ static bool parse_data(struct Parser* parser, struct Program* program) return true; } +static bool parse_enum(struct Parser* parser, struct Program* program) +{ + struct EnumDecl decl = create_enum(); + + if (!consume(parser, TOKEN_IDENTIFIER, "expected enum name after 'enum'")) + goto error; + decl.name = parser->previous; + + if (!consume(parser, TOKEN_LEFT_BRACE, "expected '{' after enum name")) + goto error; + + while (!check(parser, TOKEN_RIGHT_BRACE)) + { + if (check(parser, TOKEN_EOF)) + { + error_at(parser, parser->current, "unterminated enum"); + goto error; + } + + if (!consume(parser, TOKEN_IDENTIFIER, "expected an enum member name")) + goto error; + add_enum_member(&decl, parser->previous); + + match_token(parser, TOKEN_COMMA); + } + advance_parser(parser); + + add_enum(program, decl); + return true; + +error: + free(decl.members); + return false; +} + +static bool parse_struct(struct Parser* parser, struct Program* program) +{ + struct StructDecl decl = create_struct(); + + if (!consume(parser, TOKEN_IDENTIFIER, "expected struct name after 'struct'")) + goto error; + decl.name = parser->previous; + + if (!consume(parser, TOKEN_LEFT_BRACE, "expected '{' after struct name")) + goto error; + + while (!check(parser, TOKEN_RIGHT_BRACE)) + { + if (check(parser, TOKEN_EOF)) + { + error_at(parser, parser->current, "unterminated struct"); + goto error; + } + + struct StructField field; + if (!consume(parser, TOKEN_IDENTIFIER, "expected a field name")) + goto error; + field.name = parser->previous; + + field.size = STORE_SIZE_QWORD; + if (match_token(parser, TOKEN_COLON)) + { + field.size = parse_store_size(parser); + if (field.size == STORE_SIZE_NONE) + { + error_at(parser, parser->current, "expected a size (byte, word, dword, qword) after ':'"); + goto error; + } + } + + add_struct_field(&decl, field); + match_token(parser, TOKEN_COMMA); + } + advance_parser(parser); + + add_struct(program, decl); + return true; + +error: + free(decl.fields); + return false; +} + static bool parse_params(struct Parser* parser, struct ProcDecl* proc) { if (check(parser, TOKEN_RIGHT_PAREN)) @@ -555,6 +639,18 @@ bool parse_program(struct Lexer* lexer, struct Program* out) if (!parse_data(&parser, out)) return false; } + else if (check(&parser, TOKEN_ENUM)) + { + advance_parser(&parser); + if (!parse_enum(&parser, out)) + return false; + } + else if (check(&parser, TOKEN_STRUCT)) + { + advance_parser(&parser); + if (!parse_struct(&parser, out)) + return false; + } else if (check(&parser, TOKEN_PROC)) { advance_parser(&parser); diff --git a/src/sema/sema.c b/src/sema/sema.c index 85907e9..d2b4668 100644 --- a/src/sema/sema.c +++ b/src/sema/sema.c @@ -27,7 +27,8 @@ static struct ProcDecl* find_proc(struct Program* program, struct Token name) static bool check_duplicate_names(struct Source source, struct Program* program) { - size_t count = program->const_count + program->data_count + program->proc_count; + size_t count = program->const_count + program->data_count + + program->enum_count + program->struct_count + program->proc_count; if (count == 0) return true; @@ -43,6 +44,16 @@ static bool check_duplicate_names(struct Source source, struct Program* program) names[n] = program->data_decls[i].name; n += 1; } + for (size_t i = 0; i < program->enum_count; i += 1) + { + names[n] = program->enums[i].name; + n += 1; + } + for (size_t i = 0; i < program->struct_count; i += 1) + { + names[n] = program->structs[i].name; + n += 1; + } for (size_t i = 0; i < program->proc_count; i += 1) { names[n] = program->procs[i].name; @@ -241,6 +252,24 @@ static bool is_data(struct RefCheck* check, struct Token token) return false; } +static struct EnumDecl* find_enum(struct RefCheck* check, struct Token token) +{ + for (size_t i = 0; i < check->program->enum_count; i += 1) + if (names_equal(check->program->enums[i].name, token)) + return &check->program->enums[i]; + + return NULL; +} + +static struct StructDecl* find_struct(struct RefCheck* check, struct Token token) +{ + for (size_t i = 0; i < check->program->struct_count; i += 1) + if (names_equal(check->program->structs[i].name, token)) + return &check->program->structs[i]; + + return NULL; +} + static bool is_stack_buffer(struct RefCheck* check, struct Token token) { for (size_t i = 0; i < check->proc->body_count; i += 1) @@ -308,15 +337,48 @@ static void check_expr(struct RefCheck* check, struct Expr* expr) { if (object->kind != EXPR_PRIMARY || !is_register(check, object->primary.token)) ref_error(check, member, "size suffix requires a register"); + break; } - else if (object->kind != EXPR_PRIMARY || !is_data(check, object->primary.token)) - { - check_expr(check, object); - } - else if (!token_is(member, "len")) + + if (object->kind == EXPR_PRIMARY) { - ref_error(check, member, "unknown member '%.*s'", (int)member.length, member.start); + struct EnumDecl* enumeration = find_enum(check, object->primary.token); + if (enumeration != NULL) + { + bool found = false; + for (size_t i = 0; i < enumeration->member_count; i += 1) + if (names_equal(enumeration->members[i], member)) + found = true; + if (!found) + ref_error(check, member, "enum '%.*s' has no member '%.*s'", + (int)object->primary.token.length, object->primary.token.start, + (int)member.length, member.start); + break; + } + + struct StructDecl* layout = find_struct(check, object->primary.token); + if (layout != NULL) + { + bool found = token_is(member, "size"); + for (size_t i = 0; i < layout->field_count; i += 1) + if (names_equal(layout->fields[i].name, member)) + found = true; + if (!found) + ref_error(check, member, "struct '%.*s' has no field '%.*s'", + (int)object->primary.token.length, object->primary.token.start, + (int)member.length, member.start); + break; + } + + if (is_data(check, object->primary.token)) + { + if (!token_is(member, "len")) + ref_error(check, member, "unknown member '%.*s'", (int)member.length, member.start); + break; + } } + + check_expr(check, object); break; } } diff --git a/tests/codegen_test.c b/tests/codegen_test.c index c56ca7a..61e441f 100644 --- a/tests/codegen_test.c +++ b/tests/codegen_test.c @@ -399,8 +399,36 @@ static void test_generate_load(struct TestContext* context) free_program(&program); } +static void test_generate_enum_struct(struct TestContext* context) +{ + struct Lexer lexer = create_lexer( + "enum Color\n{\nRed,\nGreen,\nBlue\n}\n" + "struct Point\n{\nx: qword\ny: qword\nflag: byte\n}\n" + "proc main\n{\nrax = Color.Blue\nrbx = Point.y\nrcx = Point.flag\nrdx = Point.size\n}\n"); + struct Program program; + check(context, parse_program(&lexer, &program)); + + FILE* out = tmpfile(); + generate_nasm(&program, out); + fflush(out); + rewind(out); + + char buffer[1024]; + size_t read = fread(buffer, 1, sizeof(buffer) - 1, out); + buffer[read] = '\0'; + fclose(out); + + check(context, strstr(buffer, "mov rax, 2") != NULL); // Color.Blue -> 2 + check(context, strstr(buffer, "mov rbx, 8") != NULL); // Point.y -> 8 + check(context, strstr(buffer, "mov rcx, 16") != NULL); // Point.flag -> 16 + check(context, strstr(buffer, "mov rdx, 17") != NULL); // Point.size -> 17 + + free_program(&program); +} + void run_codegen_tests(struct TestContext* context) { + test_generate_enum_struct(context); test_generate_load(context); test_generate_logical_registers(context); test_generate_logical_disabled(context); diff --git a/tests/lexer_test.c b/tests/lexer_test.c index 7260d6c..38644d9 100644 --- a/tests/lexer_test.c +++ b/tests/lexer_test.c @@ -62,6 +62,13 @@ static void test_size_keywords(struct TestContext* context) check(context, scan_token(&lexer).type == TOKEN_QWORD); } +static void test_enum_struct_keywords(struct TestContext* context) +{ + struct Lexer lexer = create_lexer("enum struct"); + check(context, scan_token(&lexer).type == TOKEN_ENUM); + check(context, scan_token(&lexer).type == TOKEN_STRUCT); +} + static void test_line_counting(struct TestContext* context) { struct Lexer lexer = create_lexer("a\nb\nc"); @@ -89,6 +96,7 @@ void run_lexer_tests(struct TestContext* context) test_literals(context); test_keywords(context); test_size_keywords(context); + test_enum_struct_keywords(context); test_line_counting(context); test_comments(context); } diff --git a/tests/parser_test.c b/tests/parser_test.c index 5a45e9e..71e1433 100644 --- a/tests/parser_test.c +++ b/tests/parser_test.c @@ -267,6 +267,27 @@ static void test_parse_register_size_suffix(struct TestContext* context) free_program(&program); } +static void test_parse_enum_struct(struct TestContext* context) +{ + struct Lexer lexer = create_lexer( + "enum Color\n{\nRed,\nGreen,\nBlue\n}\nstruct Point\n{\nx\ny: byte\n}\n"); + struct Program program; + + check(context, parse_program(&lexer, &program)); + check(context, program.enum_count == 1); + check(context, text_is(program.enums[0].name, "Color")); + check(context, program.enums[0].member_count == 3); + check(context, text_is(program.enums[0].members[1], "Green")); + + check(context, program.struct_count == 1); + check(context, text_is(program.structs[0].name, "Point")); + check(context, program.structs[0].field_count == 2); + check(context, program.structs[0].fields[0].size == STORE_SIZE_QWORD); + check(context, program.structs[0].fields[1].size == STORE_SIZE_BYTE); + + free_program(&program); +} + static void test_parse_errors(struct TestContext* context) { struct Program program; @@ -300,5 +321,6 @@ void run_parser_tests(struct TestContext* context) test_parse_directives(context); test_parse_bad_directive(context); test_parse_register_size_suffix(context); + test_parse_enum_struct(context); test_parse_errors(context); } diff --git a/tests/sema_test.c b/tests/sema_test.c index 2d6e187..e89f608 100644 --- a/tests/sema_test.c +++ b/tests/sema_test.c @@ -97,6 +97,17 @@ static void test_deref_needs_register(struct TestContext* context) check(context, analyze_source("proc main\n{\nrax = ^rsi\n}\n")); } +static void test_enum_struct_members(struct TestContext* context) +{ + check(context, analyze_source( + "enum Color\n{\nRed,\nGreen\n}\nstruct Point\n{\nx\ny\n}\n" + "proc main\n{\nrax = Color.Green\nrbx = Point.y\nrcx = Point.size\n}\n")); + check(context, !analyze_source( + "enum Color\n{\nRed\n}\nproc main\n{\nrax = Color.Nope\n}\n")); + check(context, !analyze_source( + "struct Point\n{\nx\n}\nproc main\n{\nrax = Point.z\n}\n")); +} + static void test_references_resolve(struct TestContext* context) { // registers, params, consts, data (+ .len), stack buffers, labels, calls @@ -133,5 +144,6 @@ void run_sema_tests(struct TestContext* context) test_const_expr_rejects_register(context); test_const_expr_rejects_data(context); test_deref_needs_register(context); + test_enum_struct_members(context); test_references_resolve(context); } |
