diff options
author | Bad Diode <bd@badd10de.dev> | 2024-06-24 17:28:46 +0200 |
---|---|---|
committer | Bad Diode <bd@badd10de.dev> | 2024-06-24 17:28:46 +0200 |
commit | 2560da172f15982da7464a6534702c08f9b7575d (patch) | |
tree | 29dd60aeb65e40c17e52b5bad677502dbb18e163 | |
parent | 6d8b7b0bf66d22d6de13474b18617204adfe32e9 (diff) | |
download | bdl-2560da172f15982da7464a6534702c08f9b7575d.tar.gz bdl-2560da172f15982da7464a6534702c08f9b7575d.zip |
Add typechecking for enum fields
-rw-r--r-- | src/main.c | 60 | ||||
-rw-r--r-- | tests/comparisons.bad | 4 | ||||
-rw-r--r-- | tests/conditionals.bad | 8 | ||||
-rw-r--r-- | tests/semantics.bad | 43 |
4 files changed, 81 insertions, 34 deletions
@@ -6,15 +6,9 @@ | |||
6 | #include "lexer.c" | 6 | #include "lexer.c" |
7 | #include "parser.c" | 7 | #include "parser.c" |
8 | #include "vm.c" | 8 | #include "vm.c" |
9 | // TODO: To typecheck IF we need to make sure it always returns a value if we | 9 | |
10 | // are using it as an expression. Not as straightforward as I initially | 10 | // TODO: typecheck structs |
11 | // imagined! An alternative is implicit zero value in the previous case: | 11 | // TODO: add field accessor for struct/enum symbols |
12 | // | ||
13 | // let a = if (expr) 123 | ||
14 | // | ||
15 | // If the expression evalueates to `true`, a will be 123, otherwise, 0. | ||
16 | // | ||
17 | // This pattern seems quite common in practice no? | ||
18 | 12 | ||
19 | typedef enum ExecMode { | 13 | typedef enum ExecMode { |
20 | RUN_NORMAL, | 14 | RUN_NORMAL, |
@@ -69,9 +63,21 @@ typedef struct Fun { | |||
69 | Str return_type; | 63 | Str return_type; |
70 | } Fun; | 64 | } Fun; |
71 | 65 | ||
66 | typedef struct Field { | ||
67 | Str name; | ||
68 | Node *val; | ||
69 | } Field; | ||
70 | |||
71 | typedef struct Enum { | ||
72 | Str name; | ||
73 | struct FieldMap *fields; | ||
74 | } Enum; | ||
75 | |||
72 | MAPDEF(SymbolMap, symmap, Str, Symbol, str_hash, str_eq) | 76 | MAPDEF(SymbolMap, symmap, Str, Symbol, str_hash, str_eq) |
73 | MAPDEF(TypeMap, typemap, Str, Type, str_hash, str_eq) | 77 | MAPDEF(TypeMap, typemap, Str, Type, str_hash, str_eq) |
74 | MAPDEF(FunMap, funmap, Str, Fun, str_hash, str_eq) | 78 | MAPDEF(FunMap, funmap, Str, Fun, str_hash, str_eq) |
79 | MAPDEF(EnumMap, enummap, Str, Enum, str_hash, str_eq) | ||
80 | MAPDEF(FieldMap, fieldmap, Str, Field, str_hash, str_eq) | ||
75 | 81 | ||
76 | typedef struct Scope { | 82 | typedef struct Scope { |
77 | sz id; | 83 | sz id; |
@@ -85,6 +91,7 @@ typedef struct TypeScope { | |||
85 | Str name; | 91 | Str name; |
86 | TypeMap *types; | 92 | TypeMap *types; |
87 | FunMap *funcs; | 93 | FunMap *funcs; |
94 | EnumMap *enums; | ||
88 | struct TypeScope *parent; | 95 | struct TypeScope *parent; |
89 | } TypeScope; | 96 | } TypeScope; |
90 | 97 | ||
@@ -471,6 +478,37 @@ type_inference(Analyzer *a, Node *node, TypeScope *scope) { | |||
471 | node->type = cstr("nil"); | 478 | node->type = cstr("nil"); |
472 | return node->type; | 479 | return node->type; |
473 | } break; | 480 | } break; |
481 | case NODE_ENUM: { | ||
482 | node->type = cstr("nil"); | ||
483 | Str symbol = node->value.str; | ||
484 | EnumMap *e = enummap_insert(&scope->enums, symbol, | ||
485 | (Enum){.name = symbol}, a->storage); | ||
486 | for (sz i = 0; i < array_size(node->struct_field); i++) { | ||
487 | Node *field = node->struct_field[i]; | ||
488 | Str field_name = field->field_name->value.str; | ||
489 | if (fieldmap_lookup(&e->val.fields, field_name)) { | ||
490 | eprintln( | ||
491 | "%s:%d:%d: error: enum field '%s.%s' already exists", | ||
492 | a->file_name, field->line, field->col, symbol, | ||
493 | field_name); | ||
494 | } | ||
495 | if (field->field_val) { | ||
496 | Type type = type_inference(a, field->field_val, scope); | ||
497 | if (!str_eq(type, cstr("int"))) { | ||
498 | eprintln( | ||
499 | "%s:%d:%d: error: non int enum value for '%s.%s'", | ||
500 | a->file_name, field->line, field->col, symbol, | ||
501 | field_name); | ||
502 | } | ||
503 | } | ||
504 | fieldmap_insert( | ||
505 | &e->val.fields, field_name, | ||
506 | (Field){.name = field_name, .val = field->field_val}, | ||
507 | a->storage); | ||
508 | field->type = cstr("int"); | ||
509 | } | ||
510 | return node->type; | ||
511 | } break; | ||
474 | case NODE_IF: { | 512 | case NODE_IF: { |
475 | Type cond_type = type_inference(a, node->cond_if, scope); | 513 | Type cond_type = type_inference(a, node->cond_if, scope); |
476 | if (!str_eq(cond_type, cstr("bool"))) { | 514 | if (!str_eq(cond_type, cstr("bool"))) { |
@@ -566,6 +604,10 @@ type_inference(Analyzer *a, Node *node, TypeScope *scope) { | |||
566 | node->type = cstr("bool"); | 604 | node->type = cstr("bool"); |
567 | return node->type; | 605 | return node->type; |
568 | } break; | 606 | } break; |
607 | case NODE_NIL: { | ||
608 | node->type = cstr("nil"); | ||
609 | return node->type; | ||
610 | } break; | ||
569 | case NODE_NOT: | 611 | case NODE_NOT: |
570 | case NODE_AND: | 612 | case NODE_AND: |
571 | case NODE_OR: { | 613 | case NODE_OR: { |
diff --git a/tests/comparisons.bad b/tests/comparisons.bad index 7bc5d33..512feac 100644 --- a/tests/comparisons.bad +++ b/tests/comparisons.bad | |||
@@ -7,7 +7,3 @@ true != false | |||
7 | 2 >= 1 && 2 > 1 | 7 | 2 >= 1 && 2 > 1 |
8 | 3 >= 3 || 4 <= 5 | 8 | 3 >= 3 || 4 <= 5 |
9 | 4 < (5 + 6) == true | 9 | 4 < (5 + 6) == true |
10 | |||
11 | nil != false | ||
12 | nil != true | ||
13 | nil != 1 | ||
diff --git a/tests/conditionals.bad b/tests/conditionals.bad index b5ef61f..50d0cfc 100644 --- a/tests/conditionals.bad +++ b/tests/conditionals.bad | |||
@@ -5,7 +5,7 @@ if (true) "hello" | |||
5 | let a = if (2 + 2 >= 4) 42 | 5 | let a = if (2 + 2 >= 4) 42 |
6 | 6 | ||
7 | ; We support a single if expression. | 7 | ; We support a single if expression. |
8 | let b = if (0xff == 255) "hello" else "world" | 8 | let b = if (0xff == 0x32) "hello" else "world" |
9 | 9 | ||
10 | ; ... but these can compound on each other | 10 | ; ... but these can compound on each other |
11 | if (1 < 2) 6 | 11 | if (1 < 2) 6 |
@@ -51,7 +51,7 @@ match (a) { | |||
51 | ; Conditional `cond` statements are syntactic sugar for a chain of if-else | 51 | ; Conditional `cond` statements are syntactic sugar for a chain of if-else |
52 | ; statements. | 52 | ; statements. |
53 | let msg:str = cond { | 53 | let msg:str = cond { |
54 | case a == b = "hello" | 54 | a == 1 = "hello" |
55 | case a != f = "world" | 55 | a != 2 = "world" |
56 | else = "what" | 56 | else = "what" |
57 | } | 57 | } |
diff --git a/tests/semantics.bad b/tests/semantics.bad index 3479915..ee56c14 100644 --- a/tests/semantics.bad +++ b/tests/semantics.bad | |||
@@ -1,10 +1,19 @@ | |||
1 | fun add10(a: int, b: str): int { | 1 | enum weekdays { |
2 | a + 10 | 2 | mon = 1 |
3 | tue | ||
4 | wed | ||
5 | thu | ||
6 | fri | ||
7 | sat | ||
8 | sun | ||
3 | } | 9 | } |
10 | ; fun add10(a: int, b: str): int { | ||
11 | ; a + 10 | ||
12 | ; } | ||
4 | 13 | ||
5 | fun foo(): int { | 14 | ; fun foo(): int { |
6 | add10(1, "hello") | 15 | ; add10(1, "hello") |
7 | } | 16 | ; } |
8 | 17 | ||
9 | ; let a:f32 = (1.0 + 2.0 * 2.0) / 2.0 | 18 | ; let a:f32 = (1.0 + 2.0 * 2.0) / 2.0 |
10 | 19 | ||
@@ -54,18 +63,18 @@ fun foo(): int { | |||
54 | ; let a:my_struct | 63 | ; let a:my_struct |
55 | ; set a.field_a = 1 | 64 | ; set a.field_a = 1 |
56 | 65 | ||
57 | fun nested(): int { | 66 | ; fun nested(): int { |
58 | fun adder(a: u32, b: u32): u32 { | 67 | ; fun adder(a: u32, b: u32): u32 { |
59 | a + b | 68 | ; a + b |
60 | } | 69 | ; } |
61 | if (1 + 1 == 2) { | 70 | ; if (1 + 1 == 2) { |
62 | let b = 15 | 71 | ; let b = 15 |
63 | 5 + b | 72 | ; { |
64 | ; { | 73 | ; let c = 32 |
65 | ; let b = 32 | 74 | ; 5 + c |
66 | ; } | 75 | ; } |
67 | } | 76 | ; } |
68 | } | 77 | ; } |
69 | 78 | ||
70 | ; enum field { | 79 | ; enum field { |
71 | ; a | 80 | ; a |