From: Brendan Hansen Date: Mon, 14 Sep 2020 20:47:26 +0000 (-0500) Subject: added polymorphic structs; must testing will be needed X-Git-Url: https://git.brendanfh.com/?a=commitdiff_plain;h=40bb13c818e588c0a8dda1bc7106d50efe1f739e;p=onyx.git added polymorphic structs; must testing will be needed --- diff --git a/.vimspector.json b/.vimspector.json index 5cd4edea..ea4cd749 100644 --- a/.vimspector.json +++ b/.vimspector.json @@ -6,7 +6,7 @@ "type": "cppdbg", "request": "launch", "program": "${workspaceFolder}/onyx", - "args": ["-verbose", "progs/simd_test.onyx"], + "args": ["-verbose", "progs/poly_struct.onyx"], "stopAtEntry": true, "cwd": "${workspaceFolder}", "environment": [], diff --git a/CHANGELOG b/CHANGELOG new file mode 100644 index 00000000..a506ae97 --- /dev/null +++ b/CHANGELOG @@ -0,0 +1,13 @@ +Release v0.0.3 +-------------- +Additions: +* Added polymorphic structs +* Added polymorphic structs to polymorphic pattern matching + +Removals: + +Changes: + +Bug fixes: +* Fixed error message index for struct literal member type mismatch. +* Fixed freeing non-allocated memory when generating documentation. diff --git a/core/i32map.onyx b/core/i32map.onyx new file mode 100644 index 00000000..e0932fac --- /dev/null +++ b/core/i32map.onyx @@ -0,0 +1,109 @@ +package core + +I32Map :: struct ($T) { + hashes : [..] i32; + entries : [..] I32MapEntry(T); +} + +I32MapEntry :: struct ($T) { + key : i32; + next : i32; + value : T; +} + +i32map_init :: proc (imap: ^I32Map($T), hash_count: i32 = 16) { + array_init(^imap.hashes, hash_count); + array_init(^imap.entries, 4); + + for i: 0 .. hash_count do array_push(^imap.hashes, -1); +} + +i32map_free :: proc (imap: ^I32Map($T)) { + array_free(^imap.hashes); + array_free(^imap.entries); +} + +i32map_put :: proc (imap: ^I32Map($T), key: i32, value: T) { + lr := i32map_lookup(imap, key); + + if lr.entry_index >= 0 { + imap.entries[lr.entry_index].value = value; + return; + } + + entry : I32MapEntry(T); + entry.key = key; + entry.value = value; + entry.next = imap.hashes[lr.hash_index]; + + array_push(^imap.entries, entry); + + imap.hashes[lr.hash_index] = imap.entries.count - 1; +} + +i32map_has :: proc (imap: ^I32Map($T), key: i32) -> bool { + lr := i32map_lookup(imap, key); + return lr.entry_index >= 0; +} + +i32map_get :: proc (imap: ^I32Map($T), key: i32) -> i32 { + lr := i32map_lookup(imap, key); + if lr.entry_index >= 0 do return imap.entries[lr.entry_index].value; + + return null; +} + +i32map_delete :: proc (imap: ^I32Map($T), key: i32) { + lr := i32map_lookup(imap, key); + if lr.entry_index < 0 do return; + + if lr.entry_prev < 0 do imap.hashes[lr.hash_index] = imap.entries[lr.entry_index].next; + else do imap.hashes[lr.entry_prev] = imap.entries[lr.entry_index].next; + + if lr.entry_index == imap.entries.count - 1 { + array_pop(^imap.entries); + return; + } + + array_fast_delete(^imap.entries, lr.entry_index); + last := i32map_lookup(imap, imap.entries[lr.entry_index].key); + if last.entry_prev >= 0 do imap.entries[last.entry_prev].next = lr.entry_index; + else do imap.hashes[last.hash_index] = lr.entry_index; +} + +i32map_clear :: proc (imap: ^I32Map($T)) { + for i: 0 .. imap.hashes.count do imap.hashes.data[i] = -1; + imap.entries.count = 0; +} + + + +// +// Private symbols +// + +#private +I32MapLookupResult :: struct { + hash_index : i32 = -1; + entry_index : i32 = -1; + entry_prev : i32 = -1; +} + +#private +i32map_lookup :: proc (imap: ^I32Map($T), key: i32) -> I32MapLookupResult { + lr := I32MapLookupResult.{}; + + hash := cast(u32) 0xcbf29ce7 ^ cast(u32) key; + + lr.hash_index = hash % imap.hashes.count; + lr.entry_index = imap.hashes[lr.hash_index]; + + while lr.entry_index >= 0 { + if imap.entries[lr.entry_index].key == key do return lr; + + lr.entry_prev = lr.entry_index; + lr.entry_index = imap.entries[lr.entry_index].next; + } + + return lr; +} diff --git a/core/ptrmap.onyx b/core/ptrmap.onyx index 700e77b8..bf6482c6 100644 --- a/core/ptrmap.onyx +++ b/core/ptrmap.onyx @@ -25,7 +25,6 @@ ptrmap_free :: proc (use pmap: ^PtrMap) { array_free(^entries); } - ptrmap_put :: proc (use pmap: ^PtrMap, key: rawptr, value: rawptr) { lr := ptrmap_lookup(pmap, key); diff --git a/core/std/js.onyx b/core/std/js.onyx index 9df405cc..9506070c 100644 --- a/core/std/js.onyx +++ b/core/std/js.onyx @@ -4,6 +4,7 @@ package core #include_file "core/alloc" #include_file "core/array" +#include_file "core/i32map" #include_file "core/intrinsics" #include_file "core/math" #include_file "core/memory" diff --git a/core/std/wasi.onyx b/core/std/wasi.onyx index b9f6e40d..938de5fa 100644 --- a/core/std/wasi.onyx +++ b/core/std/wasi.onyx @@ -5,6 +5,7 @@ package core #include_file "core/alloc" #include_file "core/array" #include_file "core/file" +#include_file "core/i32map" #include_file "core/intrinsics" #include_file "core/math" #include_file "core/memory" diff --git a/include/onyxastnodes.h b/include/onyxastnodes.h index 6907ae26..08067a3c 100644 --- a/include/onyxastnodes.h +++ b/include/onyxastnodes.h @@ -44,6 +44,8 @@ typedef struct AstDynArrType AstDynArrType; typedef struct AstVarArgType AstVarArgType; typedef struct AstStructType AstStructType; typedef struct AstStructMember AstStructMember; +typedef struct AstPolyStructType AstPolyStructType; +typedef struct AstPolyCallType AstPolyCallType; typedef struct AstEnumType AstEnumType; typedef struct AstEnumValue AstEnumValue; typedef struct AstTypeAlias AstTypeAlias; @@ -106,6 +108,8 @@ typedef enum AstKind { Ast_Kind_DynArr_Type, Ast_Kind_VarArg_Type, Ast_Kind_Struct_Type, + Ast_Kind_Poly_Struct_Type, + Ast_Kind_Poly_Call_Type, Ast_Kind_Enum_Type, Ast_Kind_Type_Alias, Ast_Kind_Type_Raw_Alias, @@ -533,6 +537,21 @@ struct AstStructMember { AstTyped_base; AstTyped* initial_value; }; +struct AstPolyStructType { + AstType_base; + + Scope *scope; + bh_arr(OnyxToken *) poly_params; + bh_table(AstStructType *) concrete_structs; + + AstStructType* base_struct; +}; +struct AstPolyCallType { + AstType_base; + + AstType* callee; + bh_arr(AstType *) params; +}; struct AstEnumType { AstType_base; Scope *scope; @@ -749,6 +768,8 @@ typedef enum PolyProcLookupMethod { } PolyProcLookupMethod; AstFunction* polymorphic_proc_lookup(AstPolyProc* pp, PolyProcLookupMethod pp_lookup, ptr actual, OnyxFilePos pos); +AstStructType* polymorphic_struct_lookup(AstPolyStructType* ps_type, bh_arr(Type *) params); + // NOTE: Useful inlined functions static inline b32 is_lval(AstNode* node) { return (node->kind == Ast_Kind_Local) diff --git a/include/onyxsempass.h b/include/onyxsempass.h index 145795c5..d5af9acf 100644 --- a/include/onyxsempass.h +++ b/include/onyxsempass.h @@ -34,6 +34,7 @@ extern SemState semstate; // NOTE: Resolving all symbols in the tree void onyx_resolve_symbols(); +AstType* symres_type(AstType* type); void symres_function(AstFunction* func); // NOTE: Inferring and checking types in the tree diff --git a/include/onyxtypes.h b/include/onyxtypes.h index f84609ad..4be207ea 100644 --- a/include/onyxtypes.h +++ b/include/onyxtypes.h @@ -76,6 +76,7 @@ typedef struct StructMember { u16 alignment, mem_count; \ bh_table(StructMember) members; \ bh_arr(StructMember *) memarr; \ + bh_arr(Type *) poly_args; \ }) \ TYPE_KIND(Array, struct { u32 size; u32 count; Type *elem; }) \ TYPE_KIND(Slice, struct { Type *ptr_to_data; }) \ diff --git a/onyx b/onyx index f33dd0bb..c0f66a7c 100755 Binary files a/onyx and b/onyx differ diff --git a/progs/poly_struct.onyx b/progs/poly_struct.onyx new file mode 100644 index 00000000..9e32c695 --- /dev/null +++ b/progs/poly_struct.onyx @@ -0,0 +1,19 @@ +package main + +#include_file "core/std/wasi" + +use package core + + +main :: proc (args: [] cstring) { + + imap : I32Map(string); + i32map_init(^imap); + + i32map_put(^imap, 50, "Hello "); + i32map_put(^imap, 1234, "World!"); + + print(i32map_get(^imap, 50)); + print(i32map_get(^imap, 1234)); + print("\n"); +} diff --git a/src/onyx.c b/src/onyx.c index 40608c76..89bc81b8 100644 --- a/src/onyx.c +++ b/src/onyx.c @@ -279,7 +279,8 @@ static void merge_parse_results(CompilerState* compiler_state, ParseResults* res } case Ast_Kind_Type_Alias: - case Ast_Kind_Struct_Type: { + case Ast_Kind_Struct_Type: + case Ast_Kind_Poly_Struct_Type: { ent.type = Entity_Type_Type_Alias; ent.type_alias = (AstType *) node; bh_arr_push(compiler_state->prog_info.entities, ent); diff --git a/src/onyxchecker.c b/src/onyxchecker.c index 3a984c70..30d08d16 100644 --- a/src/onyxchecker.c +++ b/src/onyxchecker.c @@ -795,6 +795,8 @@ b32 check_struct_literal(AstStructLiteral* sl) { type_lookup_member_by_idx(sl->type, i, &smem); Type* formal = smem.type; + // NOTE: We may want to report a different error if the struct member was named, + // since those names may not be in the correct order. - brendanfh 2020/09/12 if (!types_are_compatible(formal, (*actual)->type)) { onyx_report_error(sl->token->pos, "Mismatched types for %d%s member, expected '%s', got '%s'.", @@ -918,6 +920,7 @@ b32 check_array_access(AstArrayAccess* aa) { b32 check_field_access(AstFieldAccess** pfield) { AstFieldAccess* field = *pfield; if (check_expression(&field->expr)) return 1; + if (field->expr->type == NULL) return 1; if (!type_is_structlike(field->expr->type)) { onyx_report_error(field->token->pos, diff --git a/src/onyxclone.c b/src/onyxclone.c index 87a43792..ab4068bc 100644 --- a/src/onyxclone.c +++ b/src/onyxclone.c @@ -12,7 +12,6 @@ static inline b32 should_clone(AstNode* node) { case Ast_Kind_NumLit: case Ast_Kind_StrLit: case Ast_Kind_Package: - case Ast_Kind_Struct_Type: case Ast_Kind_Enum_Type: case Ast_Kind_Enum_Value: case Ast_Kind_Overloaded_Function: @@ -54,6 +53,8 @@ static inline i32 ast_kind_to_size(AstNode* node) { case Ast_Kind_DynArr_Type: return sizeof(AstDynArrType); case Ast_Kind_VarArg_Type: return sizeof(AstVarArgType); case Ast_Kind_Struct_Type: return sizeof(AstStructType); + case Ast_Kind_Poly_Struct_Type: return sizeof(AstPolyStructType); + case Ast_Kind_Poly_Call_Type: return sizeof(AstPolyCallType); case Ast_Kind_Enum_Type: return sizeof(AstEnumType); case Ast_Kind_Type_Alias: return sizeof(AstTypeAlias); case Ast_Kind_Type_Raw_Alias: return sizeof(AstTypeRawAlias); @@ -273,6 +274,25 @@ AstNode* ast_clone(bh_allocator a, void* n) { ((AstTypeAlias *) nn)->to = (AstType *) ast_clone(a, ((AstTypeAlias *) node)->to); break; + case Ast_Kind_Struct_Type: { + AstStructType* ds = (AstStructType *) nn; + AstStructType* ss = (AstStructType *) node; + + ds->members = NULL; + bh_arr_new(global_heap_allocator, ds->members, bh_arr_length(ss->members)); + + bh_arr_each(AstStructMember *, smem, ss->members) { + bh_arr_push(ds->members, (AstStructMember *) ast_clone(a, *smem)); + } + + ds->stcache = NULL; + break; + } + + case Ast_Kind_Struct_Member: + ((AstStructMember *) nn)->initial_value = (AstTyped *) ast_clone(a, ((AstStructMember *) node)->initial_value); + break; + case Ast_Kind_Function_Type: ((AstFunctionType *) nn)->return_type = (AstType *) ast_clone(a, ((AstFunctionType *) node)->return_type); ((AstFunctionType *) nn)->param_count = ((AstFunctionType *) node)->param_count; diff --git a/src/onyxparser.c b/src/onyxparser.c index 67a9c17a..66ec3cb7 100644 --- a/src/onyxparser.c +++ b/src/onyxparser.c @@ -1297,6 +1297,30 @@ static AstType* parse_type(OnyxParser* parser) { *next_insertion = (AstType *) symbol_node; } + if (parser->curr->type == '(') { + consume_token(parser); + + bh_arr(AstType *) params = NULL; + bh_arr_new(global_heap_allocator, params, 2); + + while (parser->curr->type != ')') { + if (parser->hit_unexpected_token) break; + + AstType* t = parse_type(parser); + bh_arr_push(params, t); + + if (parser->curr->type != ')') + expect_token(parser, ','); + } + expect_token(parser, ')'); + + AstPolyCallType* pc_type = make_node(AstPolyCallType, Ast_Kind_Poly_Call_Type); + pc_type->callee = *next_insertion; + pc_type->params = params; + + *next_insertion = (AstType *) pc_type; + } + next_insertion = NULL; } @@ -1319,8 +1343,37 @@ static AstType* parse_type(OnyxParser* parser) { } static AstStructType* parse_struct(OnyxParser* parser) { - AstStructType* s_node = make_node(AstStructType, Ast_Kind_Struct_Type); - s_node->token = expect_token(parser, Token_Type_Keyword_Struct); + OnyxToken *s_token = expect_token(parser, Token_Type_Keyword_Struct); + + AstStructType* s_node; + AstPolyStructType* poly_struct = NULL; + + s_node = make_node(AstStructType, Ast_Kind_Struct_Type); + s_node->token = s_token; + + if (parser->curr->type == '(') { + consume_token(parser); + + bh_arr(OnyxToken *) poly_params = NULL; + bh_arr_new(global_heap_allocator, poly_params, 1); + + while (parser->curr->type == '$') { + consume_token(parser); + if (parser->hit_unexpected_token) return NULL; + + OnyxToken* sym_token = expect_token(parser, Token_Type_Symbol); + bh_arr_push(poly_params, sym_token); + + if (parser->curr->type != ')') + expect_token(parser, ','); + } + expect_token(parser, ')'); + + poly_struct = make_node(AstPolyStructType, Ast_Kind_Poly_Struct_Type); + poly_struct->token = s_token; + poly_struct->poly_params = poly_params; + poly_struct->base_struct = s_node; + } bh_arr_new(global_heap_allocator, s_node->members, 4); @@ -1380,7 +1433,13 @@ static AstStructType* parse_struct(OnyxParser* parser) { expect_token(parser, '}'); - return s_node; + if (poly_struct != NULL) { + // NOTE: Not a StructType + return (AstStructType *) poly_struct; + + } else { + return s_node; + } } // e @@ -1810,7 +1869,9 @@ static AstNode* parse_top_level_statement(OnyxParser* parser) { } else if (node->kind != Ast_Kind_Overloaded_Function && node->kind != Ast_Kind_StrLit) { - if (node->kind == Ast_Kind_Struct_Type || node->kind == Ast_Kind_Enum_Type) { + if (node->kind == Ast_Kind_Struct_Type + || node->kind == Ast_Kind_Enum_Type + || node->kind == Ast_Kind_Poly_Struct_Type) { ((AstStructType *)node)->name = bh_aprintf(global_heap_allocator, "%b", symbol->text, symbol->length); } diff --git a/src/onyxsymres.c b/src/onyxsymres.c index 9394f9c2..1c650f5b 100644 --- a/src/onyxsymres.c +++ b/src/onyxsymres.c @@ -8,7 +8,7 @@ static void scope_enter(Scope* new_scope); static void scope_leave(); -static AstType* symres_type(AstType* type); +AstType* symres_type(AstType* type); static void symres_local(AstLocal** local); static void symres_call(AstCall* call); static void symres_size_of(AstSizeOf* so); @@ -40,7 +40,7 @@ static void scope_leave() { semstate.curr_scope = semstate.curr_scope->parent; } -static AstType* symres_type(AstType* type) { +AstType* symres_type(AstType* type) { if (type == NULL) return NULL; if (type->kind == Ast_Kind_Type_Alias) { @@ -151,6 +151,25 @@ static AstType* symres_type(AstType* type) { return type; } + if (type->kind == Ast_Kind_Poly_Struct_Type) { + AstPolyStructType* pst_node = (AstPolyStructType *) type; + pst_node->scope = scope_create(semstate.node_allocator, semstate.curr_scope); + + return type; + } + + if (type->kind == Ast_Kind_Poly_Call_Type) { + AstPolyCallType* pc_node = (AstPolyCallType *) type; + + pc_node->callee = symres_type(pc_node->callee); + + bh_arr_each(AstType *, param, pc_node->params) { + *param = symres_type(*param); + } + + return type; + } + return type; } diff --git a/src/onyxtypes.c b/src/onyxtypes.c index 7b0ff821..3698df8f 100644 --- a/src/onyxtypes.c +++ b/src/onyxtypes.c @@ -423,6 +423,32 @@ Type* type_build_from_ast(bh_allocator alloc, AstType* type_node) { case Ast_Kind_Type_Raw_Alias: return ((AstTypeRawAlias *) type_node)->to; + case Ast_Kind_Poly_Struct_Type: + // @Cleanup: Replace this with a proper onyx_report_error. - brendanfh 2020/09/14 + assert(("Polymorphic struct used without instantiation.", 0)); + break; + + case Ast_Kind_Poly_Call_Type: { + AstPolyCallType* pc_type = (AstPolyCallType *) type_node; + + // @Cleanup: make this a proper error message. -brendanfh 2020/09/14 + assert(pc_type->callee && pc_type->callee->kind == Ast_Kind_Poly_Struct_Type); + + AstPolyStructType* ps_type = (AstPolyStructType *) pc_type->callee; + + bh_arr(Type *) param_types = NULL; + bh_arr_new(global_heap_allocator, param_types, bh_arr_length(pc_type->params)); + bh_arr_each(AstType *, ptype, pc_type->params) { + bh_arr_push(param_types, type_build_from_ast(alloc, *ptype)); + } + + AstStructType* concrete = polymorphic_struct_lookup(ps_type, param_types); + + bh_arr_free(param_types); + + return type_build_from_ast(alloc, (AstType *) concrete); + } + case Ast_Kind_Symbol: assert(("symbol node in type expression", 0)); return NULL; diff --git a/src/onyxutils.c b/src/onyxutils.c index 38cfc300..a19babb0 100644 --- a/src/onyxutils.c +++ b/src/onyxutils.c @@ -49,6 +49,8 @@ static const char* ast_node_names[] = { "SLICE TYPE", "DYNARR TYPE", "STRUCT TYPE", + "POLYMORPHIC STRUCT TYPE", + "POLYMORPHIC STRUCT CALL TYPE", "ENUM TYPE", "TYPE_ALIAS", "TYPE RAW ALIAS" @@ -428,6 +430,22 @@ static Type* solve_poly_type(AstNode* target, AstType* type_expr, Type* actual) break; } + case Ast_Kind_Poly_Call_Type: { + if (elem.actual->kind != Type_Kind_Struct) break; + if (bh_arr_length(elem.actual->Struct.poly_args) != bh_arr_length(((AstPolyCallType *) elem.type_expr)->params)) break; + + AstPolyCallType* pt = (AstPolyCallType *) elem.type_expr; + + fori (i, 0, bh_arr_length(pt->params)) { + bh_arr_push(elem_queue, ((PolySolveElem) { + .type_expr = pt->params[i], + .actual = elem.actual->Struct.poly_args[i], + })); + } + + break; + } + default: break; } } @@ -534,6 +552,68 @@ no_errors: return func; } + + +AstStructType* polymorphic_struct_lookup(AstPolyStructType* ps_type, bh_arr(Type *) params) { + // @Cleanup + assert(bh_arr_length(ps_type->poly_params) == bh_arr_length(params)); + assert(ps_type->scope != NULL); + + if (ps_type->concrete_structs == NULL) { + bh_table_init(global_heap_allocator, ps_type->concrete_structs, 16); + } + + scope_clear(ps_type->scope); + + fori (i, 0, bh_arr_length(ps_type->poly_params)) { + if (params[i] == NULL) { + onyx_report_error((OnyxFilePos) { 0 }, "Type parameter is not a type."); + return NULL; + } + + AstTypeRawAlias* raw = onyx_ast_node_new(semstate.node_allocator, sizeof(AstTypeRawAlias), Ast_Kind_Type_Raw_Alias); + raw->to = params[i]; + + symbol_introduce(ps_type->scope, ps_type->poly_params[i], (AstNode *) raw); + } + + static char key_buf[1024]; + fori (i, 0, 1024) key_buf[i] = 0; + bh_table_each_start(AstNode *, ps_type->scope->symbols); + strncat(key_buf, key, 1023); + strncat(key_buf, "=", 1023); + strncat(key_buf, type_get_name(((AstTypeRawAlias *) value)->to), 1023); + strncat(key_buf, ";", 1023); + bh_table_each_end; + + if (bh_table_has(AstStructType *, ps_type->concrete_structs, key_buf)) { + return bh_table_get(AstStructType *, ps_type->concrete_structs, key_buf); + } + + AstStructType* concrete_struct = (AstStructType *) ast_clone(semstate.node_allocator, ps_type->base_struct); + + semstate.curr_scope = ps_type->scope; + concrete_struct = (AstStructType *) symres_type((AstType *) concrete_struct); + if (onyx_has_errors()) goto has_error; + goto no_errors; + +has_error: + // onyx_report_error((OnyxFilePos) { 0 }, "Error in polymorphic struct generated from this call site."); + return NULL; + +no_errors: + bh_table_put(AstStructType *, ps_type->concrete_structs, key_buf, concrete_struct); + + Type* cs_type = type_build_from_ast(semstate.node_allocator, (AstType *) concrete_struct); + + cs_type->Struct.poly_args = NULL; + bh_arr_new(global_heap_allocator, cs_type->Struct.poly_args, bh_arr_length(params)); + + fori (i, 0, bh_arr_length(params)) bh_arr_push(cs_type->Struct.poly_args, params[i]); + + return concrete_struct; +} + i32 sort_entities(const void* e1, const void* e2) { return ((Entity *)e1)->type - ((Entity *)e2)->type; }