From 04036aba9c1eec05fe143d939ef93e017502f015 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 11 Aug 2019 23:58:49 +0100 Subject: [PATCH] `package reflect`; fix substring type bug; fix scoping rules for `using` on procedure parameter --- core/reflect/reflect.odin | 285 ++++++++++++++++++++++++++++++++++++++ core/runtime/core.odin | 11 +- examples/demo/demo.odin | 34 +++-- src/check_decl.cpp | 118 ++++++++++------ src/check_expr.cpp | 2 +- 5 files changed, 392 insertions(+), 58 deletions(-) create mode 100644 core/reflect/reflect.odin diff --git a/core/reflect/reflect.odin b/core/reflect/reflect.odin new file mode 100644 index 000000000..ecc2e7362 --- /dev/null +++ b/core/reflect/reflect.odin @@ -0,0 +1,285 @@ +package reflect + +import "core:runtime" +import "core:mem" + + +Type_Kind :: enum { + Invalid, + + Named, + Integer, + Rune, + Float, + Complex, + String, + Boolean, + Any, + Type_Id, + Pointer, + Procedure, + Array, + Dynamic_Array, + Slice, + Tuple, + Struct, + Union, + Enum, + Map, + Bit_Field, + Bit_Set, + Opaque, + Simd_Vector, +} + + +type_kind :: proc(T: typeid) -> Type_Kind { + ti := type_info_of(T); + if ti != nil { + #complete switch _ in ti.variant { + case runtime.Type_Info_Named: return .Named; + case runtime.Type_Info_Integer: return .Integer; + case runtime.Type_Info_Rune: return .Rune; + case runtime.Type_Info_Float: return .Float; + case runtime.Type_Info_Complex: return .Complex; + case runtime.Type_Info_String: return .String; + case runtime.Type_Info_Boolean: return .Boolean; + case runtime.Type_Info_Any: return .Any; + case runtime.Type_Info_Type_Id: return .Type_Id; + case runtime.Type_Info_Pointer: return .Pointer; + case runtime.Type_Info_Procedure: return .Procedure; + case runtime.Type_Info_Array: return .Array; + case runtime.Type_Info_Dynamic_Array: return .Dynamic_Array; + case runtime.Type_Info_Slice: return .Slice; + case runtime.Type_Info_Tuple: return .Tuple; + case runtime.Type_Info_Struct: return .Struct; + case runtime.Type_Info_Union: return .Union; + case runtime.Type_Info_Enum: return .Enum; + case runtime.Type_Info_Map: return .Map; + case runtime.Type_Info_Bit_Field: return .Bit_Field; + case runtime.Type_Info_Bit_Set: return .Bit_Set; + case runtime.Type_Info_Opaque: return .Opaque; + case runtime.Type_Info_Simd_Vector: return .Simd_Vector; + } + + } + return .Invalid; +} + +// TODO(bill): Better name +underlying_type_kind :: proc(T: typeid) -> Type_Kind { + return type_kind(runtime.typeid_base(T)); +} + +// TODO(bill): Better name +backing_type_kind :: proc(T: typeid) -> Type_Kind { + return type_kind(runtime.typeid_core(T)); +} + + + +size_of_typeid :: proc(T: typeid) -> int { + if ti := type_info_of(T); ti != nil { + return ti.size; + } + return 0; +} + +align_of_typeid :: proc(T: typeid) -> int { + if ti := type_info_of(T); ti != nil { + return ti.align; + } + return 1; +} + +to_bytes :: proc(v: any) -> []byte { + if v != nil { + sz := size_of_typeid(v.id); + return mem.slice_ptr((^byte)(v.data), sz); + } + return nil; +} + +any_data :: inline proc(v: any) -> (data: rawptr, id: typeid) { + return v.data, v.id; +} + +is_nil :: proc(v: any) -> bool { + data := to_bytes(v); + if data != nil { + return true; + } + for v in data do if v != 0 { + return false; + } + return true; +} + + +index :: proc(v: any, i: int, loc := #caller_location) -> any { + if v == nil do return nil; + + v := v; + v.id = runtime.typeid_base(v.id); + switch a in v { + case runtime.Type_Info_Array: + runtime.bounds_check_error_loc(loc, i, a.count); + offset := uintptr(a.elem.size * i); + data := rawptr(uintptr(v.data) + offset); + return any{data, a.elem.id}; + + case runtime.Type_Info_Slice: + raw := (^mem.Raw_Slice)(v.data); + runtime.bounds_check_error_loc(loc, i, raw.len); + offset := uintptr(a.elem.size * i); + data := rawptr(uintptr(raw.data) + offset); + return any{data, a.elem.id}; + + case runtime.Type_Info_Dynamic_Array: + raw := (^mem.Raw_Dynamic_Array)(v.data); + runtime.bounds_check_error_loc(loc, i, raw.len); + offset := uintptr(a.elem.size * i); + data := rawptr(uintptr(raw.data) + offset); + return any{data, a.elem.id}; + + case runtime.Type_Info_String: + if a.is_cstring do return nil; + + raw := (^mem.Raw_String)(v.data); + runtime.bounds_check_error_loc(loc, i, raw.len); + offset := uintptr(size_of(u8) * i); + data := rawptr(uintptr(raw.data) + offset); + return any{data, typeid_of(u8)}; + } + return nil; +} + + + + +Struct_Tag :: distinct string; + +Struct_Field :: struct { + name: string, + type: typeid, + tag: Struct_Tag, + offset: uintptr, +} + +struct_field_at :: proc(T: typeid, i: int) -> (field: Struct_Field) { + ti := runtime.type_info_base(type_info_of(T)); + if s, ok := ti.variant.(runtime.Type_Info_Struct); ok { + if 0 <= i && i < len(s.names) { + field.name = s.names[i]; + field.type = s.types[i].id; + field.tag = Struct_Tag(s.tags[i]); + field.offset = s.offsets[i]; + } + } + return; +} + +struct_field_by_name :: proc(T: typeid, name: string) -> (field: Struct_Field) { + ti := runtime.type_info_base(type_info_of(T)); + if s, ok := ti.variant.(runtime.Type_Info_Struct); ok { + for fname, i in s.names { + if fname == name { + field.name = s.names[i]; + field.type = s.types[i].id; + field.tag = Struct_Tag(s.tags[i]); + field.offset = s.offsets[i]; + break; + } + } + } + return; +} + + + +struct_field_names :: proc(T: typeid) -> []string { + ti := runtime.type_info_base(type_info_of(T)); + if s, ok := ti.variant.(runtime.Type_Info_Struct); ok { + return s.names; + } + return nil; +} + +struct_field_types :: proc(T: typeid) -> []^runtime.Type_Info { + ti := runtime.type_info_base(type_info_of(T)); + if s, ok := ti.variant.(runtime.Type_Info_Struct); ok { + return s.types; + } + return nil; +} + + +struct_field_tags :: proc(T: typeid) -> []Struct_Tag { + ti := runtime.type_info_base(type_info_of(T)); + if s, ok := ti.variant.(runtime.Type_Info_Struct); ok { + return transmute([]Struct_Tag)s.tags; + } + return nil; +} + +struct_field_offsets :: proc(T: typeid) -> []uintptr { + ti := runtime.type_info_base(type_info_of(T)); + if s, ok := ti.variant.(runtime.Type_Info_Struct); ok { + return s.offsets; + } + return nil; +} + + + +struct_tag_get :: proc(tag: Struct_Tag, key: string) -> (value: string) { + value, _ = struct_tag_lookup(tag, key); + return; +} + +struct_tag_lookup :: proc(tag: Struct_Tag, key: string) -> (value: string, ok: bool) { + for tag := tag; tag != ""; /**/ { + i := 0; + for i < len(tag) && tag[i] == ' ' { // Skip whitespace + i += 1; + } + tag = tag[i:]; + if len(tag) == 0 do break; + + i = 0; + loop: for i < len(tag) { + switch tag[i] { + case ':', '"': + break loop; + case 0x00 ..< ' ', 0x7f .. 0x9f: // break if control character is found + break loop; + } + i += 1; + } + + if i == 0 do break; + if i+1 >= len(tag) do break; + + if tag[i] != ':' || tag[i+1] != '"' { + break; + } + name := string(tag[:i]); + tag = tag[i+1:]; + + i = 1; + for i < len(tag) && tag[i] != '"' { // find closing quote + if tag[i] == '\\' do i += 1; // Skip escaped characters + i += 1; + } + + if i >= len(tag) do break; + + val := string(tag[:i+1]); + tag = tag[i+1:]; + + if key == name { + return val[1:i], true; + } + } + return; +} diff --git a/core/runtime/core.odin b/core/runtime/core.odin index 28767cc2d..047281b10 100644 --- a/core/runtime/core.odin +++ b/core/runtime/core.odin @@ -283,19 +283,21 @@ type_info_base :: proc "contextless" (info: ^Type_Info) -> ^Type_Info { } -type_info_base_without_enum :: proc "contextless" (info: ^Type_Info) -> ^Type_Info { +type_info_core :: proc "contextless" (info: ^Type_Info) -> ^Type_Info { if info == nil do return nil; base := info; loop: for { switch i in base.variant { - case Type_Info_Named: base = i.base; - case Type_Info_Enum: base = i.base; + case Type_Info_Named: base = i.base; + case Type_Info_Enum: base = i.base; + case Type_Info_Opaque: base = i.elem; case: break loop; } } return base; } +type_info_base_without_enum :: type_info_core; __type_info_of :: proc "contextless" (id: typeid) -> ^Type_Info { data := transmute(Typeid_Bit_Field)id; @@ -311,10 +313,11 @@ typeid_base :: proc "contextless" (id: typeid) -> typeid { ti = type_info_base(ti); return ti.id; } -typeid_base_without_enum :: proc "contextless" (id: typeid) -> typeid { +typeid_core :: proc "contextless" (id: typeid) -> typeid { ti := type_info_base_without_enum(type_info_of(id)); return ti.id; } +typeid_base_without_enum :: typeid_core; diff --git a/examples/demo/demo.odin b/examples/demo/demo.odin index e07eea7c7..157ecb9bf 100644 --- a/examples/demo/demo.odin +++ b/examples/demo/demo.odin @@ -4,6 +4,7 @@ import "core:fmt" import "core:mem" import "core:os" import "core:runtime" +import "core:reflect" when os.OS == "windows" { import "core:thread" @@ -946,26 +947,39 @@ deferred_procedure_associations :: proc() { } } -struct_field_tags :: proc() { - fmt.println("# struct_field_tags"); +reflection :: proc() { + fmt.println("# reflection"); Foo :: struct { x: int `tag1`, - y: string `tag2`, + y: string `json:"y_field"`, z: bool, // no tag } - ti := runtime.type_info_base(type_info_of(Foo)); - s := ti.variant.(runtime.Type_Info_Struct); + id := typeid_of(Foo); + names := reflect.struct_field_names(id); + types := reflect.struct_field_types(id); + tags := reflect.struct_field_tags(id); + + assert(len(names) == len(types) && len(names) == len(tags)); + fmt.println("Foo :: struct {"); - for _, i in s.names { - if tag := s.tags[i]; tag != "" { - fmt.printf("\t%s: %T `%s`,\n", s.names[i], s.types[i], tag); + for tag, i in tags { + name, type := names[i], types[i]; + if tag != "" { + fmt.printf("\t%s: %T `%s`,\n", name, type, tag); } else { - fmt.printf("\t%s: %T,\n", s.names[i], s.types[i]); + fmt.printf("\t%s: %T,\n", name, type); } } fmt.println("}"); + + + for tag, i in tags { + if val, ok := reflect.struct_tag_lookup(tag, "json"); ok { + fmt.printf("json: %s -> %s\n", names[i], val); + } + } } main :: proc() { @@ -986,6 +1000,6 @@ main :: proc() { bit_set_type(); diverging_procedures(); deferred_procedure_associations(); - struct_field_tags(); + reflection(); } } diff --git a/src/check_decl.cpp b/src/check_decl.cpp index f3e0f70bc..cf3cb8e97 100644 --- a/src/check_decl.cpp +++ b/src/check_decl.cpp @@ -1074,6 +1074,11 @@ void check_entity_decl(CheckerContext *ctx, Entity *e, DeclInfo *d, Type *named_ } +struct ProcUsingVar { + Entity *e; + Entity *uvar; +}; + void check_proc_body(CheckerContext *ctx_, Token token, DeclInfo *decl, Type *type, Ast *body) { if (body == nullptr) { @@ -1098,58 +1103,85 @@ void check_proc_body(CheckerContext *ctx_, Token token, DeclInfo *decl, Type *ty ctx->curr_proc_decl = decl; ctx->curr_proc_sig = type; - GB_ASSERT(type->kind == Type_Proc); - if (type->Proc.param_count > 0) { - TypeTuple *params = &type->Proc.params->Tuple; - for_array(i, params->variables) { - Entity *e = params->variables[i]; - if (e->kind != Entity_Variable) { - continue; - } - if (!(e->flags & EntityFlag_Using)) { - continue; - } - bool is_immutable = e->Variable.is_immutable; - bool is_value = (e->flags & EntityFlag_Value) != 0 && !is_type_pointer(e->type); - String name = e->token.string; - Type *t = base_type(type_deref(e->type)); - if (t->kind == Type_Struct) { - Scope *scope = t->Struct.scope; - if (scope == nullptr) { - scope = scope_of_node(t->Struct.node); - } - GB_ASSERT(scope != nullptr); - for_array(i, scope->elements.entries) { - Entity *f = scope->elements.entries[i].value; - if (f->kind == Entity_Variable) { - Entity *uvar = alloc_entity_using_variable(e, f->token, f->type); - uvar->Variable.is_immutable = is_immutable; - if (is_value) uvar->flags |= EntityFlag_Value; + ast_node(bs, BlockStmt, body); + + Array using_entities = {}; + using_entities.allocator = heap_allocator(); + defer (array_free(&using_entities)); + + { + GB_ASSERT(type->kind == Type_Proc); + if (type->Proc.param_count > 0) { + TypeTuple *params = &type->Proc.params->Tuple; + for_array(i, params->variables) { + Entity *e = params->variables[i]; + if (e->kind != Entity_Variable) { + continue; + } + if (!(e->flags & EntityFlag_Using)) { + continue; + } + bool is_immutable = e->Variable.is_immutable; + bool is_value = (e->flags & EntityFlag_Value) != 0 && !is_type_pointer(e->type); + String name = e->token.string; + Type *t = base_type(type_deref(e->type)); + if (t->kind == Type_Struct) { + Scope *scope = t->Struct.scope; + if (scope == nullptr) { + scope = scope_of_node(t->Struct.node); + } + GB_ASSERT(scope != nullptr); + for_array(i, scope->elements.entries) { + Entity *f = scope->elements.entries[i].value; + if (f->kind == Entity_Variable) { + Entity *uvar = alloc_entity_using_variable(e, f->token, f->type); + uvar->Variable.is_immutable = is_immutable; + if (is_value) uvar->flags |= EntityFlag_Value; + + + ProcUsingVar puv = {e, uvar}; + array_add(&using_entities, puv); - Entity *prev = scope_insert(ctx->scope, uvar); - if (prev != nullptr) { - error(e->token, "Namespace collision while 'using' '%.*s' of: %.*s", LIT(name), LIT(prev->token.string)); - break; } } + } else { + error(e->token, "'using' can only be applied to variables of type struct"); + break; } - } else { - error(e->token, "'using' can only be applied to variables of type struct"); - break; } } } - ast_node(bs, BlockStmt, body); + + for_array(i, using_entities) { + Entity *e = using_entities[i].e; + Entity *uvar = using_entities[i].uvar; + Entity *prev = scope_insert(ctx->scope, uvar); + if (prev != nullptr) { + error(e->token, "Namespace collision while 'using' '%.*s' of: %.*s", LIT(e->token.string), LIT(prev->token.string)); + break; + } + } + check_open_scope(ctx, body); - check_stmt_list(ctx, bs->stmts, Stmt_CheckScopeDecls); - if (type->Proc.result_count > 0) { - if (!check_is_terminating(body)) { - if (token.kind == Token_Ident) { - error(bs->close, "Missing return statement at the end of the procedure '%.*s'", LIT(token.string)); - } else { - // NOTE(bill): Anonymous procedure (lambda) - error(bs->close, "Missing return statement at the end of the procedure"); + { + for_array(i, using_entities) { + Entity *e = using_entities[i].e; + Entity *uvar = using_entities[i].uvar; + Entity *prev = scope_insert(ctx->scope, uvar); + // NOTE(bill): Don't err here + } + + check_stmt_list(ctx, bs->stmts, Stmt_CheckScopeDecls); + + if (type->Proc.result_count > 0) { + if (!check_is_terminating(body)) { + if (token.kind == Token_Ident) { + error(bs->close, "Missing return statement at the end of the procedure '%.*s'", LIT(token.string)); + } else { + // NOTE(bill): Anonymous procedure (lambda) + error(bs->close, "Missing return statement at the end of the procedure"); + } } } } diff --git a/src/check_expr.cpp b/src/check_expr.cpp index 99fe6695b..7a6e0f22d 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -7164,7 +7164,7 @@ ExprKind check_expr_base_internal(CheckerContext *c, Operand *o, Ast *node, Type if (o->mode == Addressing_Constant) { max_count = o->value.value_string.len; } - o->type = t_string; + o->type = type_deref(o->type); } break;