| 1 | // Copyright (c) 2020-2024 Joe Conigliaro. All rights reserved. |
| 2 | // Use of this source code is governed by an MIT license |
| 3 | // that can be found in the LICENSE file. |
| 4 | module types |
| 5 | |
| 6 | pub type Object = Const | Fn | Global | Module | SmartCastSelector | Type | TypeObject |
| 7 | |
| 8 | // pub type Object = Const | Fn | Global | Module | |
| 9 | // Alias | Array | Enum | Map | Pointer | Primitive | String | Struct | SumType |
| 10 | |
| 11 | struct SmartCastSelector { |
| 12 | origin Type |
| 13 | field string |
| 14 | cast_type Type |
| 15 | } |
| 16 | |
| 17 | pub struct TypeObject { |
| 18 | pub: |
| 19 | typ Type |
| 20 | } |
| 21 | |
| 22 | @[heap] |
| 23 | pub struct Scope { |
| 24 | pub: |
| 25 | parent &Scope = unsafe { nil } |
| 26 | pub mut: |
| 27 | objects map[string]Object |
| 28 | types map[string]Type |
| 29 | // TODO: try implement using original concept |
| 30 | field_smartcasts map[string]Type |
| 31 | // smartcasts map[string]Type |
| 32 | // TODO: it may be more efficient looking up local vars using an ID |
| 33 | // even if we had to store them in two different places. investigate. |
| 34 | // variables []Object |
| 35 | start int |
| 36 | end int |
| 37 | } |
| 38 | |
| 39 | pub fn new_scope(parent &Scope) &Scope { |
| 40 | unsafe { |
| 41 | return &Scope{ |
| 42 | parent: parent |
| 43 | } |
| 44 | } |
| 45 | } |
| 46 | |
| 47 | // same_scope_ptr compares scope identity by address instead of structural equality. |
| 48 | pub fn same_scope_ptr(a &Scope, b &Scope) bool { |
| 49 | return voidptr(a) == voidptr(b) |
| 50 | } |
| 51 | |
| 52 | // TODO: try implement the alternate method I was experimenting with (SmartCastSelector) |
| 53 | // i'm not sure if it is actually possible though. need to explore it. |
| 54 | pub fn (s &Scope) lookup_field_smartcast(name string) ?Type { |
| 55 | if !scope_lookup_string_is_valid(name) { |
| 56 | return none |
| 57 | } |
| 58 | if name in s.field_smartcasts { |
| 59 | return s.field_smartcasts[name] or { return none } |
| 60 | } |
| 61 | if s.parent != unsafe { nil } { |
| 62 | return s.parent.lookup_field_smartcast(name) |
| 63 | } |
| 64 | return none |
| 65 | } |
| 66 | |
| 67 | pub fn (s &Scope) lookup(name string) ?Object { |
| 68 | if !scope_lookup_string_is_valid(name) { |
| 69 | return none |
| 70 | } |
| 71 | if name in s.objects { |
| 72 | return s.objects[name] or { return none } |
| 73 | } |
| 74 | return none |
| 75 | } |
| 76 | |
| 77 | pub fn (s &Scope) lookup_parent(name string, pos int) ?Object { |
| 78 | if obj := s.lookup(name) { |
| 79 | return obj |
| 80 | // if !pos.is_valid() || cmpPos(obj.scopePos(), pos) <= 0 { |
| 81 | // return s, obj |
| 82 | // } |
| 83 | } |
| 84 | if s.parent != unsafe { nil } { |
| 85 | if parent_obj := s.parent.lookup_parent(name, pos) { |
| 86 | return parent_obj |
| 87 | } |
| 88 | } |
| 89 | // println('lookup_parent: NOT FOUND: ${name}') |
| 90 | return none |
| 91 | } |
| 92 | |
| 93 | pub fn (s &Scope) lookup_type(name string) ?Type { |
| 94 | if !scope_lookup_string_is_valid(name) { |
| 95 | return none |
| 96 | } |
| 97 | if name in s.types { |
| 98 | return s.types[name] or { return none } |
| 99 | } |
| 100 | return none |
| 101 | } |
| 102 | |
| 103 | pub fn (s &Scope) lookup_type_parent(name string, pos int) ?Type { |
| 104 | if typ := s.lookup_type(name) { |
| 105 | return typ |
| 106 | // if !pos.is_valid() || cmpPos(obj.scopePos(), pos) <= 0 { |
| 107 | // return s, obj |
| 108 | // } |
| 109 | } |
| 110 | if s.parent != unsafe { nil } { |
| 111 | if parent_typ := s.parent.lookup_type_parent(name, pos) { |
| 112 | return parent_typ |
| 113 | } |
| 114 | } |
| 115 | return none |
| 116 | } |
| 117 | |
| 118 | // lookup_var_type looks up a variable by name and returns its type. |
| 119 | // Walks up the scope chain to find the variable. |
| 120 | pub fn (s &Scope) lookup_var_type(name string) ?Type { |
| 121 | if obj := s.lookup_parent(name, 0) { |
| 122 | return obj.typ() |
| 123 | } |
| 124 | return none |
| 125 | } |
| 126 | |
| 127 | pub fn (s &Scope) lookup_parent_with_scope(name string, pos int) ?(&Scope, Object) { |
| 128 | if obj := s.lookup(name) { |
| 129 | return s, obj |
| 130 | // if !pos.is_valid() || cmpPos(obj.scopePos(), pos) <= 0 { |
| 131 | // return s, obj |
| 132 | // } |
| 133 | } |
| 134 | if s.parent != unsafe { nil } { |
| 135 | if parent_scope, parent_obj := s.parent.lookup_parent_with_scope(name, pos) { |
| 136 | return parent_scope, parent_obj |
| 137 | } |
| 138 | } |
| 139 | // println('lookup_parent: NOT FOUND: ${name}') |
| 140 | return none |
| 141 | } |
| 142 | |
| 143 | pub fn (mut s Scope) insert(name string, obj Object) { |
| 144 | if !scope_lookup_string_is_valid(name) { |
| 145 | return |
| 146 | } |
| 147 | trace_scope_fixed_array_object('insert_in', name, obj) |
| 148 | if typ := object_decl_type(obj) { |
| 149 | s.types[name] = typ |
| 150 | } |
| 151 | if name in s.objects { |
| 152 | existing := s.objects[name] or { return } |
| 153 | // Module scopes pre-register a self-module placeholder so code can |
| 154 | // reference `mod_name.CONST` from inside the same module. A real symbol |
| 155 | // with the same name should override that placeholder. |
| 156 | if existing is Module && obj !is Module { |
| 157 | s.objects[name] = obj |
| 158 | } |
| 159 | return |
| 160 | } |
| 161 | s.objects[name] = obj |
| 162 | if stored := s.objects[name] { |
| 163 | trace_scope_fixed_array_object('insert_out', name, stored) |
| 164 | } |
| 165 | } |
| 166 | |
| 167 | // insert_or_update always overwrites an existing entry. Used for fn_root_scope |
| 168 | // where variables from nested scopes must be updated when re-declared. |
| 169 | pub fn (mut s Scope) insert_or_update(name string, obj Object) { |
| 170 | if !scope_lookup_string_is_valid(name) { |
| 171 | return |
| 172 | } |
| 173 | trace_scope_fixed_array_object('insert_update_in', name, obj) |
| 174 | if typ := object_decl_type(obj) { |
| 175 | s.types[name] = typ |
| 176 | } |
| 177 | s.objects[name] = obj |
| 178 | if stored := s.objects[name] { |
| 179 | trace_scope_fixed_array_object('insert_update_out', name, stored) |
| 180 | } |
| 181 | } |
| 182 | |
| 183 | fn trace_scope_fixed_array_object(_label string, _name string, _obj Object) { |
| 184 | } |
| 185 | |
| 186 | pub fn (mut s Scope) insert_type(name string, typ Type) { |
| 187 | if !scope_lookup_string_is_valid(name) { |
| 188 | return |
| 189 | } |
| 190 | s.types[name] = typ |
| 191 | } |
| 192 | |
| 193 | fn object_decl_type(obj Object) ?Type { |
| 194 | match obj { |
| 195 | Type { |
| 196 | return obj |
| 197 | } |
| 198 | else {} |
| 199 | } |
| 200 | |
| 201 | return none |
| 202 | } |
| 203 | |
| 204 | fn scope_lookup_string_is_valid(s string) bool { |
| 205 | if s.len <= 0 || s.len > 512 { |
| 206 | return false |
| 207 | } |
| 208 | ptr := unsafe { u64(voidptr(s.str)) } |
| 209 | return ptr > 4096 |
| 210 | } |
| 211 | |
| 212 | pub fn (s &Scope) print(recurse_parents bool) { |
| 213 | println('# SCOPE:') |
| 214 | for name, obj in s.objects { |
| 215 | println(' * ${name}: ${obj.type_name()}') |
| 216 | // if obj is Type { |
| 217 | // println(' - ${name}: ${obj.type_name()}') |
| 218 | // } |
| 219 | } |
| 220 | if recurse_parents && s.parent != unsafe { nil } { |
| 221 | s.parent.print(recurse_parents) |
| 222 | } |
| 223 | } |
| 224 | |
| 225 | pub fn (obj &Object) typ() Type { |
| 226 | match obj { |
| 227 | Const { |
| 228 | return obj.typ |
| 229 | } |
| 230 | Fn { |
| 231 | return obj.typ |
| 232 | } |
| 233 | Global { |
| 234 | return obj.typ |
| 235 | } |
| 236 | Module { |
| 237 | // TODO: modules don't have a type, return a placeholder |
| 238 | return Type(u16_) |
| 239 | } |
| 240 | SmartCastSelector { |
| 241 | return obj.origin |
| 242 | } |
| 243 | Type { |
| 244 | return obj |
| 245 | } |
| 246 | TypeObject { |
| 247 | return obj.typ |
| 248 | } |
| 249 | } |
| 250 | } |
| 251 | |