module clox.container.table; import core.stdc.string; import core.stdc.stdlib; import clox.object; import clox.value; import clox.memory; enum TABLE_MAX_LOAD = 0.75; struct Table{ size_t count, capacity; Entry* entries; struct Entry{ Obj.String* key; Value value; } void initialise(){ count = capacity = 0; entries = null; } void free(){ if(entries !is null) FREE_ARRAY!Entry(entries, capacity); initialise(); } private void adjustCapacity(size_t cap){ Entry* ent = ALLOCATE!Entry(cap); for(int i = 0; i < cap; i++){ ent[i].key = null; ent[i].value = Value.init; } count = 0; for(int i = 0; i < capacity; i++){ Entry* entry = &entries[i]; if(entry.key is null) continue; Entry* dest = findEntry(entries, capacity, entry.key); dest.key = entry.key; dest.value = entry.value; count++; } if(entries !is null) FREE_ARRAY!Entry(entries, capacity); entries = ent; capacity = cap; } private Entry* findEntry(Entry* entries, size_t capacity, in Obj.String* key){ uint index = key.hash % capacity; Entry* tombstone = null; while(true){ Entry* entry = &entries[index]; if(entry.key is null){ if(entry.value.isType(Value.Type.None)){ return tombstone !is null ? tombstone : entry; } else { if(tombstone is null) tombstone = entry; } } else if(entry.key is key){ return entry; } index = (index + 1) % capacity; } } bool set(Obj.String* key, Value value){ if(count + 1 > capacity * TABLE_MAX_LOAD){ size_t cap = GROW_CAPACITY(capacity); adjustCapacity(cap); } Entry* entry = findEntry(entries, capacity, key); bool isNewKey = entry.key == null; if(isNewKey && entry.value.isType(Value.Type.None)) count++; entry.key = key; entry.value = value; return isNewKey; } bool get(in Obj.String* key, out Value value){ if(count == 0) return false; Entry* entry = findEntry(entries, capacity, key); if(entry.key is null) return false; value = entry.value; return true; } bool del(in Obj.String* key){ if(count == 0) return false; Entry* entry = findEntry(entries, capacity, key); if(entry.key is null) return false; entry.key = null; entry.value = Value.from(true); return true; } void addAll(Table* from){ for(int i = 0; i < from.capacity; i++){ Entry* entry = &from.entries[i]; if(entry.key !is null) set(entry.key, entry.value); } } Obj.String* findString(const char[] chars, uint hash){ if(count == 0) return null; uint index = hash % capacity; while(true){ Entry* entry = &entries[index]; if(entry.key == null){ if(entry.value.isType(Value.Type.None)) return null; } else if(entry.key.hash == hash && entry.key.chars == chars){ return entry.key; } index = (index + 1) % capacity; } } } unittest{ import clox.vm : vm; Table tbl; tbl.initialise(); scope(exit){ tbl.free(); vm.free(); } assert(tbl.count == 0); Obj.String* str = Obj.String.copy("hello"); tbl.set(str, Value.from(true)); assert(tbl.count == 1); assert(tbl.findString("hello", str.hash) is str); Value val; assert(tbl.get(str, val)); assert(val.asBoolean == true); assert(tbl.del(str)); assert(tbl.get(str, val) == false); assert(val.isType(Value.Type.None)); assert(tbl.count == 1); foreach(i; 0..25){ Obj.String* str2 = Obj.String.copy("hi 2"); tbl.set(str2, Value.from(i)); assert(tbl.get(str2, val)); assert(val.asNumber == i); } assert(tbl.get(str, val) == false); tbl.set(str, Value.from(true)); assert(tbl.get(str, val)); assert(val.asBoolean == true); } uint jenkinsOneAtATimeHash(T)(in T[] key){ uint hash = 0; foreach(v; key){ hash += v; hash += hash << 10; hash ^= hash >> 6; } hash += hash << 3; hash ^= hash >> 11; hash += hash << 15; return hash; } alias hashString = jenkinsOneAtATimeHash;