mirror of https://github.com/japl-lang/japl.git
Added simpleHashMap for internal use
This commit is contained in:
parent
a767e44daa
commit
a14d9a7882
|
@ -42,13 +42,13 @@ type
|
|||
## a tombstone instead of something like detecting
|
||||
## tombstones as entries with null keys but full values
|
||||
## may seem wasteful. The thing is, though, that since
|
||||
## we want to implement sets on top of this hashmap and
|
||||
## we want to implement sets on top of this hashmap and
|
||||
## the implementation of a set is *literally* a dictionary
|
||||
## with empty values and keys as the elements, this would
|
||||
## with empty values and keys as the elements, this would
|
||||
## confuse our findEntry method and would force us to override
|
||||
## it to account for a different behavior.
|
||||
## Using a third field takes up more space, but saves us
|
||||
## from the hassle of rewriting code
|
||||
## from the hassle of rewriting code
|
||||
key: Option[K]
|
||||
value: Option[V]
|
||||
tombstone: bool
|
||||
|
@ -248,4 +248,4 @@ proc `$`*[K, V](self: ptr HashMap[K, V]): string =
|
|||
|
||||
|
||||
proc typeName*[K, V](self: ptr HashMap[K, V]): string =
|
||||
result = "dict"
|
||||
result = "dict"
|
||||
|
|
|
@ -0,0 +1,169 @@
|
|||
# Copyright 2020 Mattia Giambirtone
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
|
||||
# This module implements a very simple associative array meant for internal
|
||||
# use by the JAPL runtime. This "version" of the HashMap object is optimized
|
||||
# to store JAPL objects only and to not use anything from nim's stdlib other
|
||||
# than the system module. For a documented & more flexible hashmap type
|
||||
# check src/types/hashMap.nim
|
||||
|
||||
|
||||
import ../memory
|
||||
import ../config
|
||||
import baseObject
|
||||
import methods
|
||||
import iterable
|
||||
|
||||
|
||||
type
|
||||
SimpleEntry = object
|
||||
key: ptr Obj
|
||||
value: ptr Obj
|
||||
tombstone: bool
|
||||
SimpleHashMap* = object of Iterable
|
||||
entries: ptr UncheckedArray[ptr SimpleEntry]
|
||||
actual_length: int
|
||||
|
||||
|
||||
proc newSimpleHashMap*(): ptr SimpleHashMap =
|
||||
result = allocateObj(SimpleHashMap, ObjectType.Dict)
|
||||
result.actual_length = 0
|
||||
result.entries = nil
|
||||
result.capacity = 0
|
||||
result.length = 0
|
||||
|
||||
|
||||
proc freeSimpleHashMap*(self: ptr SimpleHashMap) =
|
||||
discard freeArray(UncheckedArray[ptr SimpleEntry], self.entries, self.capacity)
|
||||
self.length = 0
|
||||
self.actual_length = 0
|
||||
self.capacity = 0
|
||||
self.entries = nil
|
||||
|
||||
|
||||
proc findEntry(self: ptr UncheckedArray[ptr SimpleEntry], key: ptr Obj, capacity: int): ptr SimpleEntry =
|
||||
var capacity = uint64(capacity)
|
||||
var idx = uint64(key.hash()) mod capacity
|
||||
while true:
|
||||
result = self[idx]
|
||||
if system.`==`(result.key, nil) or result.tombstone:
|
||||
break
|
||||
elif result.key == key:
|
||||
break
|
||||
idx = (idx + 1) mod capacity
|
||||
|
||||
|
||||
proc adjustCapacity(self: ptr SimpleHashMap) =
|
||||
var newCapacity = growCapacity(self.capacity)
|
||||
var entries = allocate(UncheckedArray[ptr SimpleEntry], SimpleEntry, newCapacity)
|
||||
var oldEntry: ptr SimpleEntry
|
||||
var newEntry: ptr SimpleEntry
|
||||
self.length = 0
|
||||
for x in countup(0, newCapacity - 1):
|
||||
entries[x] = allocate(SimpleEntry, SimpleEntry, 1)
|
||||
entries[x].tombstone = false
|
||||
entries[x].key = nil
|
||||
entries[x].value = nil
|
||||
for x in countup(0, self.capacity - 1):
|
||||
oldEntry = self.entries[x]
|
||||
if not system.`==`(oldEntry.key, nil):
|
||||
newEntry = entries.findEntry(oldEntry.key, newCapacity)
|
||||
newEntry.key = oldEntry.key
|
||||
newEntry.value = oldEntry.value
|
||||
self.length += 1
|
||||
discard freeArray(UncheckedArray[ptr SimpleEntry], self.entries, self.capacity)
|
||||
self.entries = entries
|
||||
self.capacity = newCapacity
|
||||
|
||||
|
||||
proc setEntry(self: ptr SimpleHashMap, key: ptr Obj, value: ptr Obj): bool =
|
||||
if float64(self.length + 1) >= float64(self.capacity) * MAP_LOAD_FACTOR:
|
||||
self.adjustCapacity()
|
||||
var entry = findEntry(self.entries, key, self.capacity)
|
||||
result = system.`==`(entry.key, nil)
|
||||
if result:
|
||||
self.actual_length += 1
|
||||
self.length += 1
|
||||
entry.key = key
|
||||
entry.value = value
|
||||
entry.tombstone = false
|
||||
|
||||
|
||||
proc `[]`*(self: ptr SimpleHashMap, key: ptr Obj): ptr Obj =
|
||||
var entry = findEntry(self.entries, key, self.capacity)
|
||||
if system.`==`(entry.key, nil) or entry.tombstone:
|
||||
raise newException(KeyError, "Key not found: " & $key)
|
||||
result = entry.value
|
||||
|
||||
|
||||
proc `[]=`*(self: ptr SimpleHashMap, key: ptr Obj, value: ptr Obj) =
|
||||
discard self.setEntry(key, value)
|
||||
|
||||
|
||||
proc len*(self: ptr SimpleHashMap): int =
|
||||
result = self.actual_length
|
||||
|
||||
|
||||
proc del*(self: ptr SimpleHashMap, key: ptr Obj) =
|
||||
if self.len() == 0:
|
||||
raise newException(KeyError, "delete from empty hashmap")
|
||||
var entry = findEntry(self.entries, key, self.capacity)
|
||||
if not system.`==`(entry.key, nil):
|
||||
self.actual_length -= 1
|
||||
entry.tombstone = true
|
||||
else:
|
||||
raise newException(KeyError, "Key not found: " & $key)
|
||||
|
||||
|
||||
proc contains*(self: ptr SimpleHashMap, key: ptr Obj): bool =
|
||||
let entry = findEntry(self.entries, key, self.capacity)
|
||||
if not system.`==`(entry.key, nil) and not entry.tombstone:
|
||||
result = true
|
||||
else:
|
||||
result = false
|
||||
|
||||
|
||||
iterator keys*(self: ptr SimpleHashMap): ptr Obj =
|
||||
var entry: ptr SimpleEntry
|
||||
for i in countup(0, self.capacity - 1):
|
||||
entry = self.entries[i]
|
||||
if not system.`==`(entry.key, nil) and not entry.tombstone:
|
||||
yield entry.key
|
||||
|
||||
|
||||
iterator values*(self: ptr SimpleHashMap): ptr Obj =
|
||||
for key in self.keys():
|
||||
yield self[key]
|
||||
|
||||
|
||||
iterator pairs*(self: ptr SimpleHashMap): tuple[key: ptr Obj, val: ptr Obj] =
|
||||
for key in self.keys():
|
||||
yield (key: key, val: self[key])
|
||||
|
||||
|
||||
iterator items*(self: ptr SimpleHashMap): ptr Obj =
|
||||
for k in self.keys():
|
||||
yield k
|
||||
|
||||
|
||||
proc `$`*(self: ptr SimpleHashMap): string =
|
||||
var i = 0
|
||||
result &= "{"
|
||||
for key, value in self.pairs():
|
||||
result &= $key & ": " & $value
|
||||
if i < self.len() - 1:
|
||||
result &= ", "
|
||||
i += 1
|
||||
result &= "}"
|
18
src/vm.nim
18
src/vm.nim
|
@ -36,7 +36,7 @@ import types/typeutils
|
|||
import types/function
|
||||
import types/native
|
||||
import types/arrayList
|
||||
import types/hashMap
|
||||
import types/simpleHashMap
|
||||
import multibyte
|
||||
when DEBUG_TRACE_VM:
|
||||
import util/debug
|
||||
|
@ -63,7 +63,7 @@ type
|
|||
frames*: ptr ArrayList[CallFrame]
|
||||
stack*: ptr ArrayList[ptr Obj]
|
||||
objects*: ptr ArrayList[ptr Obj]
|
||||
globals*: ptr HashMap[string, ptr Obj]
|
||||
globals*: ptr SimpleHashMap
|
||||
cached*: array[6, ptr Obj]
|
||||
file*: ptr String
|
||||
|
||||
|
@ -256,7 +256,7 @@ proc callObject(self: var VM, callee: ptr Obj, argCount: uint8): bool =
|
|||
|
||||
proc defineGlobal*(self: var VM, name: string, value: ptr Obj) =
|
||||
## Adds a key-value couple to the VM's global scope
|
||||
self.globals[name] = value
|
||||
self.globals[name.asStr()] = value
|
||||
|
||||
|
||||
proc readByte(self: CallFrame): uint8 =
|
||||
|
@ -632,12 +632,12 @@ proc run(self: var VM): InterpretResult =
|
|||
return RuntimeError
|
||||
of OpCode.DefineGlobal:
|
||||
# Defines a global variable
|
||||
var name = frame.readConstant().toStr()
|
||||
var name = cast[ptr String](frame.readConstant())
|
||||
self.globals[name] = self.peek(0)
|
||||
discard self.pop()
|
||||
of OpCode.GetGlobal:
|
||||
# Retrieves a global variable
|
||||
var constant = frame.readConstant().toStr()
|
||||
var constant = cast[ptr String](frame.readConstant())
|
||||
if constant notin self.globals:
|
||||
self.error(newReferenceError(&"undefined name '{constant}'"))
|
||||
return RuntimeError
|
||||
|
@ -645,7 +645,7 @@ proc run(self: var VM): InterpretResult =
|
|||
self.push(self.globals[constant])
|
||||
of OpCode.SetGlobal:
|
||||
# Changes the value of an already defined global variable
|
||||
var constant = frame.readConstant().toStr()
|
||||
var constant = cast[ptr String](frame.readConstant())
|
||||
if constant notin self.globals:
|
||||
self.error(newReferenceError(&"assignment to undeclared name '{constant}'"))
|
||||
return RuntimeError
|
||||
|
@ -654,7 +654,7 @@ proc run(self: var VM): InterpretResult =
|
|||
of OpCode.DeleteGlobal:
|
||||
# Deletes a global variable
|
||||
# TODO: Inspect potential issues with the GC
|
||||
var constant = frame.readConstant().toStr()
|
||||
var constant = cast[ptr String](frame.readConstant())
|
||||
if constant notin self.globals:
|
||||
self.error(newReferenceError(&"undefined name '{constant}'"))
|
||||
return RuntimeError
|
||||
|
@ -806,7 +806,7 @@ proc initVM*(): VM =
|
|||
## and internal data structures
|
||||
when DEBUG_TRACE_VM:
|
||||
echo &"DEBUG - VM: Initializing the virtual machine, {JAPL_VERSION_STRING}"
|
||||
result = VM(globals: newHashMap[string, ptr Obj]())
|
||||
result = VM(globals: newSimpleHashMap())
|
||||
result.initStack()
|
||||
result.initCache()
|
||||
result.initStdlib()
|
||||
|
@ -816,7 +816,6 @@ proc initVM*(): VM =
|
|||
echo &"DEBUG - VM: Initialization complete, compiled with the following constants: FRAMES_MAX={FRAMES_MAX}, ARRAY_GROW_FACTOR={ARRAY_GROW_FACTOR}, MAP_LOAD_FACTOR={MAP_LOAD_FACTOR}"
|
||||
|
||||
|
||||
|
||||
proc interpret*(self: var VM, source: string, file: string): InterpretResult =
|
||||
## Interprets a source string containing JAPL code
|
||||
when DEBUG_TRACE_VM:
|
||||
|
@ -858,3 +857,4 @@ proc interpret*(self: var VM, source: string, file: string): InterpretResult =
|
|||
return RuntimeError
|
||||
when DEBUG_TRACE_VM:
|
||||
echo &"DEBUG - VM: Result -> {result}"
|
||||
|
||||
|
|
Loading…
Reference in New Issue