mirror of https://github.com/japl-lang/japl.git
Moved to custom hashmap
This commit is contained in:
parent
cd180fe4d7
commit
22d6408236
2
build.py
2
build.py
|
@ -196,7 +196,7 @@ def build(path: str, flags: Optional[Dict[str, str]] = {}, options: Optional[Dic
|
||||||
logging.debug("Running tests")
|
logging.debug("Running tests")
|
||||||
start = time()
|
start = time()
|
||||||
# TODO: Find a better way of running the test suite
|
# TODO: Find a better way of running the test suite
|
||||||
process = run_command(f"{tests_path} {'-e' if verbose else ''}", mode="run", shell=True, stderr=PIPE)
|
process = run_command(f"{tests_path} {'--stdout' if verbose else ''}", mode="run", shell=True, stderr=PIPE)
|
||||||
if status != 0:
|
if status != 0:
|
||||||
logging.error(f"Command '{command}' exited with non-0 exit code {status}, output below:\n{stderr.decode()}")
|
logging.error(f"Command '{command}' exited with non-0 exit code {status}, output below:\n{stderr.decode()}")
|
||||||
return False
|
return False
|
||||||
|
|
|
@ -31,7 +31,7 @@ import types/function
|
||||||
import types/numbers
|
import types/numbers
|
||||||
import types/japlString
|
import types/japlString
|
||||||
import types/iterable
|
import types/iterable
|
||||||
import types/arraylist
|
import types/arrayList
|
||||||
import config
|
import config
|
||||||
when isMainModule:
|
when isMainModule:
|
||||||
import util/debug
|
import util/debug
|
||||||
|
|
14
src/japl.nim
14
src/japl.nim
|
@ -18,6 +18,7 @@
|
||||||
import strformat
|
import strformat
|
||||||
import parseopt
|
import parseopt
|
||||||
import os
|
import os
|
||||||
|
import options
|
||||||
|
|
||||||
import config
|
import config
|
||||||
import vm
|
import vm
|
||||||
|
@ -28,7 +29,6 @@ import types/methods
|
||||||
import jale/editor
|
import jale/editor
|
||||||
import jale/templates
|
import jale/templates
|
||||||
import jale/plugin/defaults
|
import jale/plugin/defaults
|
||||||
import jale/plugin/history
|
|
||||||
import jale/plugin/editor_history
|
import jale/plugin/editor_history
|
||||||
|
|
||||||
|
|
||||||
|
@ -40,10 +40,12 @@ proc getLineEditor: LineEditor =
|
||||||
result.bindHistory(hist) # set default history keybindings
|
result.bindHistory(hist) # set default history keybindings
|
||||||
|
|
||||||
|
|
||||||
proc repl(bytecodeVM: VM) =
|
proc repl(vmObj: Option[VM]) =
|
||||||
var bytecodeVM = bytecodeVM
|
var bytecodeVM = VM()
|
||||||
if bytecodeVM == nil:
|
if vmObj.isNone():
|
||||||
bytecodeVM = initVM()
|
bytecodeVM = initVM()
|
||||||
|
else:
|
||||||
|
bytecodeVM = vmObj.get()
|
||||||
echo JAPL_VERSION_STRING
|
echo JAPL_VERSION_STRING
|
||||||
let nimDetails = &"[Nim {NimVersion} on {hostOs} ({hostCPU})]"
|
let nimDetails = &"[Nim {NimVersion} on {hostOs} ({hostCPU})]"
|
||||||
echo nimDetails
|
echo nimDetails
|
||||||
|
@ -72,7 +74,7 @@ proc repl(bytecodeVM: VM) =
|
||||||
proc main(file: var string = "", fromString: bool = false, interactive: bool = false) =
|
proc main(file: var string = "", fromString: bool = false, interactive: bool = false) =
|
||||||
var source: string
|
var source: string
|
||||||
if file == "" and not fromString:
|
if file == "" and not fromString:
|
||||||
repl(nil)
|
repl(none(VM))
|
||||||
return # We exit after the REPL has ran
|
return # We exit after the REPL has ran
|
||||||
if not fromString:
|
if not fromString:
|
||||||
var sourceFile: File
|
var sourceFile: File
|
||||||
|
@ -91,7 +93,7 @@ proc main(file: var string = "", fromString: bool = false, interactive: bool = f
|
||||||
var bytecodeVM = initVM()
|
var bytecodeVM = initVM()
|
||||||
discard bytecodeVM.interpret(source, file)
|
discard bytecodeVM.interpret(source, file)
|
||||||
if interactive:
|
if interactive:
|
||||||
repl(bytecodeVM)
|
repl(some(bytecodeVM))
|
||||||
bytecodeVM.freeVM()
|
bytecodeVM.freeVM()
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
|
|
||||||
import ../types/function
|
import ../types/function
|
||||||
import ../types/baseObject
|
import ../types/baseObject
|
||||||
import ../types/arraylist
|
import ../types/arrayList
|
||||||
{.experimental: "implicitDeref".}
|
{.experimental: "implicitDeref".}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
## A chunk is a piece of bytecode together with its constants
|
## A chunk is a piece of bytecode together with its constants
|
||||||
|
|
||||||
import ../types/baseObject
|
import ../types/baseObject
|
||||||
import ../types/arraylist
|
import ../types/arrayList
|
||||||
|
|
||||||
type
|
type
|
||||||
Chunk* = object
|
Chunk* = object
|
||||||
|
|
|
@ -28,6 +28,7 @@ import strformat
|
||||||
import parseutils
|
import parseutils
|
||||||
import strutils
|
import strutils
|
||||||
|
|
||||||
|
|
||||||
template join(args: seq[ptr Obj]): string =
|
template join(args: seq[ptr Obj]): string =
|
||||||
## A template that returns the string
|
## A template that returns the string
|
||||||
## representation of all args separated
|
## representation of all args separated
|
||||||
|
@ -41,6 +42,7 @@ template join(args: seq[ptr Obj]): string =
|
||||||
res = res & arg.stringify()
|
res = res & arg.stringify()
|
||||||
res
|
res
|
||||||
|
|
||||||
|
|
||||||
proc natPrint*(args: seq[ptr Obj]): tuple[kind: retNative, result: ptr Obj] =
|
proc natPrint*(args: seq[ptr Obj]): tuple[kind: retNative, result: ptr Obj] =
|
||||||
## Native function print
|
## Native function print
|
||||||
## Prints an object representation
|
## Prints an object representation
|
||||||
|
@ -53,6 +55,7 @@ proc natPrint*(args: seq[ptr Obj]): tuple[kind: retNative, result: ptr Obj] =
|
||||||
echo join(args)
|
echo join(args)
|
||||||
return (kind: retNative.Nil, result: nil)
|
return (kind: retNative.Nil, result: nil)
|
||||||
|
|
||||||
|
|
||||||
proc natPrintErr*(args: seq[ptr Obj]): tuple[kind:
|
proc natPrintErr*(args: seq[ptr Obj]): tuple[kind:
|
||||||
retNative, result: ptr Obj] =
|
retNative, result: ptr Obj] =
|
||||||
## Native function printErr
|
## Native function printErr
|
||||||
|
@ -63,6 +66,7 @@ proc natPrintErr*(args: seq[ptr Obj]): tuple[kind:
|
||||||
writeLine stderr, join(args)
|
writeLine stderr, join(args)
|
||||||
return (kind: retNative.Nil, result: nil)
|
return (kind: retNative.Nil, result: nil)
|
||||||
|
|
||||||
|
|
||||||
proc natReadline*(args: seq[ptr Obj]): tuple[kind: retNative, result: ptr Obj] =
|
proc natReadline*(args: seq[ptr Obj]): tuple[kind: retNative, result: ptr Obj] =
|
||||||
## Native function readline
|
## Native function readline
|
||||||
## Reads a line from stdin and returns
|
## Reads a line from stdin and returns
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import baseObject
|
import baseObject
|
||||||
import ../meta/opcode
|
import ../meta/opcode
|
||||||
import japlString
|
import japlString
|
||||||
import arraylist
|
import arrayList
|
||||||
|
|
||||||
|
|
||||||
type
|
type
|
||||||
|
|
|
@ -13,6 +13,12 @@
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
|
|
||||||
|
# This module implements a very simple (yet hella fast!) associative array.
|
||||||
|
# Although this module is *meant* to be used for JAPL only, the implementation
|
||||||
|
# allows for any nim type to be stored in it thanks to the options module. You
|
||||||
|
# could literally replace nim's tables implementation with this and get identical
|
||||||
|
# behavior (well, assuming the GC doesn't fuck you up, which it probably will)
|
||||||
|
|
||||||
|
|
||||||
import ../memory
|
import ../memory
|
||||||
import ../config
|
import ../config
|
||||||
|
@ -20,28 +26,40 @@ import baseObject
|
||||||
import methods
|
import methods
|
||||||
import iterable
|
import iterable
|
||||||
|
|
||||||
|
# We import just the *BARE* minimum for this bad boy to work,
|
||||||
import lenientops
|
# since we want as little interference from nim's own GC
|
||||||
|
# as possible. This code might need slight modifications to work
|
||||||
|
# outside of the JAPL runtime
|
||||||
import options
|
import options
|
||||||
import hashes
|
import hashes
|
||||||
import strformat
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
type
|
type
|
||||||
Entry*[K, V] = object
|
Entry[K, V] = object
|
||||||
## Low-level object to store key/value pairs
|
## Low-level object to store key/value pairs.
|
||||||
key*: Option[K]
|
## Using an extra value for marking the entry as
|
||||||
value*: Option[V]
|
## a tombstone instead of something like detecting
|
||||||
tombstone*: bool
|
## 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
|
||||||
|
## the implementation of a set is *literally* a dictionary
|
||||||
|
## 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
|
||||||
|
key: Option[K]
|
||||||
|
value: Option[V]
|
||||||
|
tombstone: bool
|
||||||
HashMap*[K, V] = object of Iterable
|
HashMap*[K, V] = object of Iterable
|
||||||
## An associative array with O(1) lookup time,
|
## An associative array with O(1) lookup time,
|
||||||
## similar to nim's Table type, but using raw
|
## similar to nim's Table type, but using raw
|
||||||
## memory to be more compatible with JAPL's runtime
|
## memory to be more compatible with JAPL's runtime
|
||||||
## memory management
|
## memory management
|
||||||
entries*: ptr UncheckedArray[ptr Entry[K, V]]
|
entries: ptr UncheckedArray[ptr Entry[K, V]]
|
||||||
# This attribute counts *only* non-deleted entries
|
# This attribute counts *only* non-deleted entries
|
||||||
actual_length*: int
|
actual_length: int
|
||||||
|
|
||||||
|
|
||||||
proc newHashMap*[K, V](): ptr HashMap[K, V] =
|
proc newHashMap*[K, V](): ptr HashMap[K, V] =
|
||||||
|
@ -67,16 +85,19 @@ proc findEntry[K, V](self: ptr UncheckedArray[ptr Entry[K, V]], key: K, capacity
|
||||||
## array, returns a pointer to an entry
|
## array, returns a pointer to an entry
|
||||||
var capacity = uint64(capacity)
|
var capacity = uint64(capacity)
|
||||||
var idx = uint64(key.hash()) mod capacity
|
var idx = uint64(key.hash()) mod capacity
|
||||||
var tombstone: ptr Entry[K, V] = nil
|
|
||||||
while true:
|
while true:
|
||||||
result = self[idx]
|
result = self[idx]
|
||||||
if result.key.isNone() and result.key.isSome():
|
if result.key.isNone() or result.tombstone:
|
||||||
# We found a tombstone
|
# If we got here, we either found an
|
||||||
tombstone = result
|
# empty bucket or a tombstone. In both cases,
|
||||||
elif result.key.isNone() or result.key.get() == key:
|
# we're done so we just make sure to reset
|
||||||
if tombstone != nil:
|
# the tombstone field of the entry and just
|
||||||
result = tombstone
|
# exit the loop
|
||||||
result.tombstone = true
|
break
|
||||||
|
elif result.key.get() == key:
|
||||||
|
# This if will never error out because if
|
||||||
|
# an entry is a tombstone, its values are
|
||||||
|
# also nullified
|
||||||
break
|
break
|
||||||
# If none of these conditions match, we have a collision!
|
# If none of these conditions match, we have a collision!
|
||||||
# This means we can just move on to the next slot in our probe
|
# This means we can just move on to the next slot in our probe
|
||||||
|
@ -84,7 +105,7 @@ proc findEntry[K, V](self: ptr UncheckedArray[ptr Entry[K, V]], key: K, capacity
|
||||||
# mechanism works makes the empty slot invariant easy to
|
# mechanism works makes the empty slot invariant easy to
|
||||||
# maintain since we increase the underlying array's size
|
# maintain since we increase the underlying array's size
|
||||||
# before we are actually full
|
# before we are actually full
|
||||||
idx += 1 mod capacity
|
idx = (idx + 1) mod capacity
|
||||||
|
|
||||||
|
|
||||||
proc adjustCapacity[K, V](self: ptr HashMap[K, V]) =
|
proc adjustCapacity[K, V](self: ptr HashMap[K, V]) =
|
||||||
|
@ -92,18 +113,20 @@ proc adjustCapacity[K, V](self: ptr HashMap[K, V]) =
|
||||||
## for more entries. Low-level method, not recommended
|
## for more entries. Low-level method, not recommended
|
||||||
var newCapacity = growCapacity(self.capacity)
|
var newCapacity = growCapacity(self.capacity)
|
||||||
var entries = allocate(UncheckedArray[ptr Entry[K, V]], Entry[K, V], newCapacity)
|
var entries = allocate(UncheckedArray[ptr Entry[K, V]], Entry[K, V], newCapacity)
|
||||||
|
var oldEntry: ptr Entry[K, V]
|
||||||
|
var newEntry: ptr Entry[K, V]
|
||||||
self.length = 0
|
self.length = 0
|
||||||
var temp: ptr Entry[K, V]
|
|
||||||
for x in countup(0, newCapacity - 1):
|
for x in countup(0, newCapacity - 1):
|
||||||
entries[x] = allocate(Entry[K, V], Entry[K, V], 1)
|
entries[x] = allocate(Entry[K, V], Entry[K, V], 1)
|
||||||
temp = entries[x]
|
entries[x].tombstone = false
|
||||||
temp.key = none(K)
|
entries[x].key = none(K)
|
||||||
temp.value = none(V)
|
entries[x].value = none(V)
|
||||||
temp.tombstone = false
|
for x in countup(0, self.capacity - 1):
|
||||||
for x in countdown(self.capacity - 1, 0):
|
oldEntry = self.entries[x]
|
||||||
temp = self.entries[x]
|
if oldEntry.key.isSome():
|
||||||
if temp.key.isSome():
|
newEntry = entries.findEntry(oldEntry.key.get(), newCapacity)
|
||||||
entries[x] = temp
|
newEntry.key = oldEntry.key
|
||||||
|
newEntry.value = oldEntry.value
|
||||||
self.length += 1
|
self.length += 1
|
||||||
discard freeArray(UncheckedArray[ptr Entry[K, V]], self.entries, self.capacity)
|
discard freeArray(UncheckedArray[ptr Entry[K, V]], self.entries, self.capacity)
|
||||||
self.entries = entries
|
self.entries = entries
|
||||||
|
@ -112,7 +135,11 @@ proc adjustCapacity[K, V](self: ptr HashMap[K, V]) =
|
||||||
|
|
||||||
proc setEntry[K, V](self: ptr HashMap[K, V], key: K, value: V): bool =
|
proc setEntry[K, V](self: ptr HashMap[K, V], key: K, value: V): bool =
|
||||||
## Low-level method to set/replace an entry with a value
|
## Low-level method to set/replace an entry with a value
|
||||||
if self.length + 1 > self.capacity * MAP_LOAD_FACTOR:
|
|
||||||
|
# This seems a bit stupid, but since we want as little interference
|
||||||
|
# from nim's runtime as possible, instead of using the lenientops
|
||||||
|
# module we just convert all integers to float and yolo it
|
||||||
|
if float64(self.length + 1) >= float64(self.capacity) * MAP_LOAD_FACTOR:
|
||||||
# Since we always need at least some empty slots
|
# Since we always need at least some empty slots
|
||||||
# for our probe sequences to work properly, we
|
# for our probe sequences to work properly, we
|
||||||
# always resize our underlying array before we're full.
|
# always resize our underlying array before we're full.
|
||||||
|
@ -127,13 +154,16 @@ proc setEntry[K, V](self: ptr HashMap[K, V], key: K, value: V): bool =
|
||||||
self.length += 1
|
self.length += 1
|
||||||
entry.key = some(key)
|
entry.key = some(key)
|
||||||
entry.value = some(value)
|
entry.value = some(value)
|
||||||
|
# Now we can make the new entry an actual full bucket
|
||||||
|
# and remove the tombstone flag
|
||||||
|
entry.tombstone = false
|
||||||
|
|
||||||
|
|
||||||
proc `[]`*[K, V](self: ptr HashMap[K, V], key: K): V =
|
proc `[]`*[K, V](self: ptr HashMap[K, V], key: K): V =
|
||||||
## Retrieves a value by key
|
## Retrieves a value by key
|
||||||
var entry = findEntry(self.entries, key, self.capacity)
|
var entry = findEntry(self.entries, key, self.capacity)
|
||||||
if entry.key.isNone():
|
if entry.key.isNone() or entry.tombstone:
|
||||||
raise newException(KeyError, &"Key not found: {key}")
|
raise newException(KeyError, "Key not found: " & $key)
|
||||||
result = entry.value.get()
|
result = entry.value.get()
|
||||||
|
|
||||||
|
|
||||||
|
@ -149,37 +179,41 @@ proc del*[K, V](self: ptr HashMap[K, V], key: K) =
|
||||||
raise newException(KeyError, &"delete from empty hashmap")
|
raise newException(KeyError, &"delete from empty hashmap")
|
||||||
var entry = findEntry(self.entries, key, self.capacity)
|
var entry = findEntry(self.entries, key, self.capacity)
|
||||||
if entry.key.isSome():
|
if entry.key.isSome():
|
||||||
## We don't reset the value of the
|
|
||||||
## 'value' attribute because that
|
|
||||||
## makes us understand that this
|
|
||||||
## entry is a tombstone and not
|
|
||||||
## a truly full bucket
|
|
||||||
self.actual_length -= 1
|
self.actual_length -= 1
|
||||||
entry.key = none(K)
|
entry.tombstone = true
|
||||||
else:
|
else:
|
||||||
raise newException(KeyError, &"Key not found: {key}")
|
raise newException(KeyError, "Key not found: " & $key)
|
||||||
|
|
||||||
|
|
||||||
proc contains*[K, V](self: ptr HashMap[K, V], key: K): bool =
|
proc contains*[K, V](self: ptr HashMap[K, V], key: K): bool =
|
||||||
## Checks if key is in the hashmap
|
## Checks if key is in the hashmap
|
||||||
var entry = findEntry(self.entries, key, self.capacity)
|
let entry = findEntry(self.entries, key, self.capacity)
|
||||||
if entry.key.isSome():
|
if entry.key.isSome() and not entry.tombstone:
|
||||||
result = true
|
result = true
|
||||||
else:
|
else:
|
||||||
result = false
|
result = false
|
||||||
|
|
||||||
|
|
||||||
iterator keys*[K, V](self: ptr HashMap[K, V]): K =
|
iterator keys*[K, V](self: ptr HashMap[K, V]): K =
|
||||||
## Yields all the keys in the hashmap
|
## Yields all the keys in the hashmap. This
|
||||||
|
## is the lowest-level iterator we have and it's
|
||||||
|
## the only one actually dealing with pointers
|
||||||
|
## and all that good stuff. All other iterators
|
||||||
|
## are based on this
|
||||||
var entry: ptr Entry[K, V]
|
var entry: ptr Entry[K, V]
|
||||||
for i in countup(0, self.capacity - 1):
|
for i in countup(0, self.capacity - 1):
|
||||||
entry = self.entries[i]
|
entry = self.entries[i]
|
||||||
if entry.key.isSome():
|
if entry.key.isSome() and not entry.tombstone:
|
||||||
yield entry.key.get()
|
yield entry.key.get()
|
||||||
|
|
||||||
|
|
||||||
iterator values*[K, V](self: ptr HashMap[K, V]): V =
|
iterator values*[K, V](self: ptr HashMap[K, V]): V =
|
||||||
## Yields all the values in the hashmap
|
## Yields all the values in the hashmap.
|
||||||
|
## This could *technically* be slightly more
|
||||||
|
## efficient if we just iterated over our
|
||||||
|
## entries directly, but if we can't take
|
||||||
|
## advantage of our constant lookup time
|
||||||
|
## then what's the point? :)
|
||||||
for key in self.keys():
|
for key in self.keys():
|
||||||
yield self[key]
|
yield self[key]
|
||||||
|
|
||||||
|
@ -214,17 +248,4 @@ proc `$`*[K, V](self: ptr HashMap[K, V]): string =
|
||||||
|
|
||||||
|
|
||||||
proc typeName*[K, V](self: ptr HashMap[K, V]): string =
|
proc typeName*[K, V](self: ptr HashMap[K, V]): string =
|
||||||
result = "dict"
|
result = "dict"
|
||||||
|
|
||||||
|
|
||||||
var d = newHashMap[int, int]()
|
|
||||||
d[1] = 55
|
|
||||||
d[2] = 876
|
|
||||||
d[3] = 7890
|
|
||||||
d[4] = 55
|
|
||||||
d[5] = 435
|
|
||||||
d[6] = 567
|
|
||||||
d[7] = 21334 ## Adjust capacity (75% full)
|
|
||||||
d[8] = 9768
|
|
||||||
d[9] = 235
|
|
||||||
echo d
|
|
|
@ -46,3 +46,5 @@ proc hash*(self: ptr Nil): uint64 =
|
||||||
proc eq*(self, other: ptr Nil): bool =
|
proc eq*(self, other: ptr Nil): bool =
|
||||||
result = true
|
result = true
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -28,7 +28,7 @@ import typeutils
|
||||||
import ../config
|
import ../config
|
||||||
import ../memory
|
import ../memory
|
||||||
import ../meta/opcode
|
import ../meta/opcode
|
||||||
import arraylist
|
import arrayList
|
||||||
import exception
|
import exception
|
||||||
|
|
||||||
import strformat
|
import strformat
|
||||||
|
|
|
@ -48,7 +48,7 @@ proc isCallable*(obj: ptr Obj): bool =
|
||||||
result = obj.kind in {ObjectType.Function, ObjectType.Class, ObjectType.Native}
|
result = obj.kind in {ObjectType.Function, ObjectType.Class, ObjectType.Native}
|
||||||
|
|
||||||
|
|
||||||
proc isNil*(obj: ptr Obj): bool =
|
proc isJaplNil*(obj: ptr Obj): bool =
|
||||||
## Returns true if the given obj
|
## Returns true if the given obj
|
||||||
## is a JAPL nil object
|
## is a JAPL nil object
|
||||||
result = obj.kind == ObjectType.Nil
|
result = obj.kind == ObjectType.Nil
|
||||||
|
|
|
@ -18,19 +18,21 @@
|
||||||
import ../meta/opcode
|
import ../meta/opcode
|
||||||
import ../types/baseObject
|
import ../types/baseObject
|
||||||
import ../types/methods
|
import ../types/methods
|
||||||
import ../types/arraylist
|
import ../types/arrayList
|
||||||
import strformat
|
import strformat
|
||||||
import terminal
|
import terminal
|
||||||
import ../multibyte
|
import ../multibyte
|
||||||
|
|
||||||
|
|
||||||
proc printName(name: string) =
|
proc printName(name: string) =
|
||||||
setForegroundColor(fgGreen)
|
setForegroundColor(fgRed)
|
||||||
write stdout, name
|
write stdout, name
|
||||||
setForegroundColor(fgDefault)
|
setForegroundColor(fgGreen)
|
||||||
|
|
||||||
proc nl =
|
proc nl =
|
||||||
write stdout, "\n"
|
write stdout, "\n"
|
||||||
|
|
||||||
|
|
||||||
proc simpleInstruction(name: string, index: int): int =
|
proc simpleInstruction(name: string, index: int): int =
|
||||||
write stdout, &"DEBUG - VM:\tInstruction -> "
|
write stdout, &"DEBUG - VM:\tInstruction -> "
|
||||||
printName(name)
|
printName(name)
|
||||||
|
@ -54,10 +56,19 @@ proc constantInstruction(name: string, chunk: Chunk, offset: int): int =
|
||||||
copyMem(constant.addr, constantArray.addr, sizeof(constantArray))
|
copyMem(constant.addr, constantArray.addr, sizeof(constantArray))
|
||||||
write stdout, &"DEBUG - VM:\tInstruction -> "
|
write stdout, &"DEBUG - VM:\tInstruction -> "
|
||||||
printName(name)
|
printName(name)
|
||||||
write stdout, &", points to slot {constant}"
|
write stdout, &", points to slot "
|
||||||
|
setForegroundColor(fgYellow)
|
||||||
|
write stdout, &"{constant}"
|
||||||
nl()
|
nl()
|
||||||
let obj = chunk.consts[constant]
|
let obj = chunk.consts[constant]
|
||||||
echo &"DEBUG - VM:\tOperand -> {stringify(obj)}\nDEBUG - VM:\tValue kind -> {obj.kind}"
|
setForegroundColor(fgGreen)
|
||||||
|
stdout.write(&"DEBUG - VM:\tOperand -> ")
|
||||||
|
setForegroundColor(fgYellow)
|
||||||
|
stdout.write(&"{stringify(obj)}")
|
||||||
|
setForegroundColor(fgGreen)
|
||||||
|
stdout.write("\nDEBUG - VM:\tValue kind -> ")
|
||||||
|
setForegroundColor(fgYellow)
|
||||||
|
stdout.write(&"{obj.kind}\n")
|
||||||
return offset + 4
|
return offset + 4
|
||||||
|
|
||||||
|
|
||||||
|
@ -72,7 +83,15 @@ proc jumpInstruction(name: string, chunk: Chunk, offset: int): int =
|
||||||
|
|
||||||
proc disassembleInstruction*(chunk: Chunk, offset: int): int =
|
proc disassembleInstruction*(chunk: Chunk, offset: int): int =
|
||||||
## Takes one bytecode instruction and prints it
|
## Takes one bytecode instruction and prints it
|
||||||
echo &"DEBUG - VM:\tOffset: {offset}\nDEBUG - VM:\tLine: {chunk.lines[offset]}"
|
setForegroundColor(fgGreen)
|
||||||
|
stdout.write(&"DEBUG - VM:\tOffset: ")
|
||||||
|
setForegroundColor(fgYellow)
|
||||||
|
stdout.write(&"{offset}")
|
||||||
|
setForegroundColor(fgGreen)
|
||||||
|
stdout.write("\nDEBUG - VM:\tLine: ")
|
||||||
|
setForegroundColor(fgYellow)
|
||||||
|
stdout.write(&"{chunk.lines[offset]}\n")
|
||||||
|
setForegroundColor(fgGreen)
|
||||||
var opcode = OpCode(chunk.code[offset])
|
var opcode = OpCode(chunk.code[offset])
|
||||||
case opcode:
|
case opcode:
|
||||||
of simpleInstructions:
|
of simpleInstructions:
|
||||||
|
|
90
src/vm.nim
90
src/vm.nim
|
@ -18,8 +18,6 @@
|
||||||
|
|
||||||
## Standard library imports
|
## Standard library imports
|
||||||
import strformat
|
import strformat
|
||||||
import tables
|
|
||||||
import std/enumerate
|
|
||||||
## Our modules
|
## Our modules
|
||||||
import config
|
import config
|
||||||
when not SKIP_STDLIB_INIT:
|
when not SKIP_STDLIB_INIT:
|
||||||
|
@ -37,16 +35,16 @@ import types/methods
|
||||||
import types/typeutils
|
import types/typeutils
|
||||||
import types/function
|
import types/function
|
||||||
import types/native
|
import types/native
|
||||||
import types/arraylist
|
import types/arrayList
|
||||||
|
import types/hashMap
|
||||||
import multibyte
|
import multibyte
|
||||||
# We always import it to
|
# We always import it to
|
||||||
# avoid the compiler complaining
|
# avoid the compiler complaining
|
||||||
# about functions not existing
|
# about functions not existing
|
||||||
# in production builds
|
# in production builds
|
||||||
import util/debug
|
|
||||||
|
|
||||||
when DEBUG_TRACE_VM:
|
when DEBUG_TRACE_VM:
|
||||||
import terminal
|
import util/debug
|
||||||
|
import terminal
|
||||||
|
|
||||||
|
|
||||||
type
|
type
|
||||||
|
@ -57,7 +55,7 @@ type
|
||||||
Ok,
|
Ok,
|
||||||
CompileError,
|
CompileError,
|
||||||
RuntimeError
|
RuntimeError
|
||||||
VM* = ref object
|
VM* = object
|
||||||
## A wrapper around the virtual machine
|
## A wrapper around the virtual machine
|
||||||
## functionality. Using custom heap allocated
|
## functionality. Using custom heap allocated
|
||||||
## types for everything might sound excessive,
|
## types for everything might sound excessive,
|
||||||
|
@ -69,7 +67,7 @@ type
|
||||||
frames*: ptr ArrayList[CallFrame]
|
frames*: ptr ArrayList[CallFrame]
|
||||||
stack*: ptr ArrayList[ptr Obj]
|
stack*: ptr ArrayList[ptr Obj]
|
||||||
objects*: ptr ArrayList[ptr Obj]
|
objects*: ptr ArrayList[ptr Obj]
|
||||||
globals*: Table[string, ptr Obj] # TODO: Custom hashmap
|
globals*: ptr HashMap[string, ptr Obj]
|
||||||
cached*: array[6, ptr Obj]
|
cached*: array[6, ptr Obj]
|
||||||
file*: ptr String
|
file*: ptr String
|
||||||
|
|
||||||
|
@ -81,7 +79,7 @@ func handleInterrupt() {.noconv.} =
|
||||||
raise newException(KeyboardInterrupt, "Ctrl+C")
|
raise newException(KeyboardInterrupt, "Ctrl+C")
|
||||||
|
|
||||||
|
|
||||||
proc initStack*(self: VM) =
|
proc initStack*(self: var VM) =
|
||||||
## Initializes the VM's stack, frame stack
|
## Initializes the VM's stack, frame stack
|
||||||
## and objects arraylist
|
## and objects arraylist
|
||||||
when DEBUG_TRACE_VM:
|
when DEBUG_TRACE_VM:
|
||||||
|
@ -190,7 +188,7 @@ proc peek*(self: VM, distance: int): ptr Obj =
|
||||||
return self.stack[self.stack.high() - distance]
|
return self.stack[self.stack.high() - distance]
|
||||||
|
|
||||||
|
|
||||||
proc call(self: VM, function: ptr Function, argCount: int): bool =
|
proc call(self: var VM, function: ptr Function, argCount: int): bool =
|
||||||
## Sets up the call frame and performs error checking
|
## Sets up the call frame and performs error checking
|
||||||
## when calling callables
|
## when calling callables
|
||||||
if argCount < function.arity:
|
if argCount < function.arity:
|
||||||
|
@ -211,7 +209,7 @@ proc call(self: VM, function: ptr Function, argCount: int): bool =
|
||||||
return true
|
return true
|
||||||
|
|
||||||
|
|
||||||
proc call(self: VM, native: ptr Native, argCount: int): bool =
|
proc call(self: var VM, native: ptr Native, argCount: int): bool =
|
||||||
## Does the same as self.call, but with native functions
|
## Does the same as self.call, but with native functions
|
||||||
if argCount != native.arity and native.arity != -1:
|
if argCount != native.arity and native.arity != -1:
|
||||||
self.error(newTypeError(&"function '{stringify(native.name)}' takes {native.arity} argument(s), got {argCount}"))
|
self.error(newTypeError(&"function '{stringify(native.name)}' takes {native.arity} argument(s), got {argCount}"))
|
||||||
|
@ -245,7 +243,7 @@ proc call(self: VM, native: ptr Native, argCount: int): bool =
|
||||||
return true
|
return true
|
||||||
|
|
||||||
|
|
||||||
proc callObject(self: VM, callee: ptr Obj, argCount: uint8): bool =
|
proc callObject(self: var VM, callee: ptr Obj, argCount: uint8): bool =
|
||||||
## Wrapper around call() to do type checking
|
## Wrapper around call() to do type checking
|
||||||
if callee.isCallable():
|
if callee.isCallable():
|
||||||
case callee.kind:
|
case callee.kind:
|
||||||
|
@ -260,7 +258,7 @@ proc callObject(self: VM, callee: ptr Obj, argCount: uint8): bool =
|
||||||
return false
|
return false
|
||||||
|
|
||||||
|
|
||||||
proc defineGlobal*(self: VM, name: string, value: ptr Obj) =
|
proc defineGlobal*(self: var VM, name: string, value: ptr Obj) =
|
||||||
## Adds a key-value couple to the VM's global scope
|
## Adds a key-value couple to the VM's global scope
|
||||||
self.globals[name] = value
|
self.globals[name] = value
|
||||||
|
|
||||||
|
@ -300,46 +298,77 @@ when DEBUG_TRACE_VM:
|
||||||
## state of the virtual machine
|
## state of the virtual machine
|
||||||
|
|
||||||
let view = frame.getView()
|
let view = frame.getView()
|
||||||
setForegroundColor(fgYellow)
|
setForegroundColor(fgMagenta)
|
||||||
|
if iteration > 1:
|
||||||
|
echo "" # To separate different iterations
|
||||||
stdout.write("DEBUG - VM: General information\n")
|
stdout.write("DEBUG - VM: General information\n")
|
||||||
stdout.write(&"DEBUG - VM:\tIteration -> {iteration}\n")
|
setForegroundColor(fgGreen)
|
||||||
setForegroundColor(fgDefault)
|
stdout.write(&"DEBUG - VM:\tIteration -> ")
|
||||||
stdout.write("DEBUG - VM:\tStack -> [")
|
setForegroundColor(fgYellow)
|
||||||
|
stdout.write(&"{iteration}\n")
|
||||||
|
setForegroundColor(fgGreen)
|
||||||
|
stdout.write("DEBUG - VM:\tStack -> ")
|
||||||
|
setForegroundColor(fgYellow)
|
||||||
|
stdout.write("[")
|
||||||
for i, v in self.stack:
|
for i, v in self.stack:
|
||||||
stdout.write(stringify(v))
|
stdout.write(stringify(v))
|
||||||
if i < self.stack.high():
|
if i < self.stack.high():
|
||||||
stdout.write(", ")
|
stdout.write(", ")
|
||||||
stdout.write("]\nDEBUG - VM: \tGlobals -> {")
|
stdout.write("]")
|
||||||
for i, (k, v) in enumerate(self.globals.pairs()):
|
setForegroundColor(fgGreen)
|
||||||
|
stdout.write("\nDEBUG - VM: \tGlobals -> ")
|
||||||
|
setForegroundColor(fgYellow)
|
||||||
|
stdout.write("{")
|
||||||
|
var i = 0
|
||||||
|
for k, v in self.globals.pairs():
|
||||||
stdout.write(&"'{k}': {stringify(v)}")
|
stdout.write(&"'{k}': {stringify(v)}")
|
||||||
if i < self.globals.len() - 1:
|
if i < self.globals.len() - 1:
|
||||||
stdout.write(", ")
|
stdout.write(", ")
|
||||||
stdout.write("}\nDEBUG - VM: Frame information\n")
|
i += 1
|
||||||
|
stdout.write("}")
|
||||||
|
setForegroundColor(fgMagenta)
|
||||||
|
stdout.write("\nDEBUG - VM: Frame information\n")
|
||||||
|
setForegroundColor(fgGreen)
|
||||||
stdout.write("DEBUG - VM:\tType -> ")
|
stdout.write("DEBUG - VM:\tType -> ")
|
||||||
|
setForegroundColor(fgYellow)
|
||||||
if frame.function.name == nil:
|
if frame.function.name == nil:
|
||||||
stdout.write("main\n")
|
stdout.write("main\n")
|
||||||
else:
|
else:
|
||||||
stdout.write(&"function, '{frame.function.name.stringify()}'\n")
|
stdout.write(&"function, '{frame.function.name.stringify()}'\n")
|
||||||
echo &"DEBUG - VM:\tCount -> {self.frames.len()}"
|
setForegroundColor(fgGreen)
|
||||||
echo &"DEBUG - VM:\tLength -> {view.len}"
|
stdout.write(&"DEBUG - VM:\tCount -> ")
|
||||||
|
setForegroundColor(fgYellow)
|
||||||
|
stdout.write(&"{self.frames.len()}\n")
|
||||||
|
setForegroundColor(fgGreen)
|
||||||
|
stdout.write(&"DEBUG - VM:\tLength -> ")
|
||||||
|
setForegroundColor(fgYellow)
|
||||||
|
stdout.write(&"{view.len}\n")
|
||||||
|
setForegroundColor(fgGreen)
|
||||||
stdout.write("DEBUG - VM:\tTable -> ")
|
stdout.write("DEBUG - VM:\tTable -> ")
|
||||||
|
setForegroundColor(fgYellow)
|
||||||
stdout.write("[")
|
stdout.write("[")
|
||||||
for i, e in frame.function.chunk.consts:
|
for i, e in frame.function.chunk.consts:
|
||||||
stdout.write(stringify(e))
|
stdout.write(stringify(e))
|
||||||
if i < len(frame.function.chunk.consts) - 1:
|
if i < len(frame.function.chunk.consts) - 1:
|
||||||
stdout.write(", ")
|
stdout.write(", ")
|
||||||
stdout.write("]\nDEBUG - VM:\tStack view -> ")
|
stdout.write("]")
|
||||||
|
setForegroundColor(fgGreen)
|
||||||
|
stdout.write("\nDEBUG - VM:\tStack view -> ")
|
||||||
|
setForegroundColor(fgYellow)
|
||||||
stdout.write("[")
|
stdout.write("[")
|
||||||
for i, e in view:
|
for i, e in view:
|
||||||
stdout.write(stringify(e))
|
stdout.write(stringify(e))
|
||||||
if i < len(view) - 1:
|
if i < len(view) - 1:
|
||||||
stdout.write(", ")
|
stdout.write(", ")
|
||||||
stdout.write("]\n")
|
stdout.write("]\n")
|
||||||
|
setForegroundColor(fgMagenta)
|
||||||
echo "DEBUG - VM: Current instruction"
|
echo "DEBUG - VM: Current instruction"
|
||||||
|
setForegroundColor(fgGreen)
|
||||||
discard disassembleInstruction(frame.function.chunk, frame.ip - 1)
|
discard disassembleInstruction(frame.function.chunk, frame.ip - 1)
|
||||||
|
setForegroundColor(fgDefault)
|
||||||
|
|
||||||
|
|
||||||
proc run(self: VM): InterpretResult =
|
proc run(self: var VM): InterpretResult =
|
||||||
## Chews trough bytecode instructions executing
|
## Chews trough bytecode instructions executing
|
||||||
## them one at a time: this is the runtime's
|
## them one at a time: this is the runtime's
|
||||||
## main loop
|
## main loop
|
||||||
|
@ -487,9 +516,6 @@ proc run(self: VM): InterpretResult =
|
||||||
self.error(newTypeError(&"unsupported binary operator '**' for objects of type '{left.typeName()}' and '{right.typeName()}'"))
|
self.error(newTypeError(&"unsupported binary operator '**' for objects of type '{left.typeName()}' and '{right.typeName()}'"))
|
||||||
return RuntimeError
|
return RuntimeError
|
||||||
of OpCode.True:
|
of OpCode.True:
|
||||||
## TODO: Make sure that even operations that can yield
|
|
||||||
## preallocated types, but do not have access to the VM,
|
|
||||||
## yield these cached types
|
|
||||||
self.push(cast[ptr Bool](self.getBoolean(true)))
|
self.push(cast[ptr Bool](self.getBoolean(true)))
|
||||||
of OpCode.False:
|
of OpCode.False:
|
||||||
self.push(cast[ptr Bool](self.getBoolean(false)))
|
self.push(cast[ptr Bool](self.getBoolean(false)))
|
||||||
|
@ -571,7 +597,7 @@ proc run(self: VM): InterpretResult =
|
||||||
# This is implemented internally for obvious
|
# This is implemented internally for obvious
|
||||||
# reasons and works on any pair of objects, which
|
# reasons and works on any pair of objects, which
|
||||||
# is why we call nim's system.== operator and NOT
|
# is why we call nim's system.== operator and NOT
|
||||||
# our custom one
|
# our custom one's
|
||||||
var right = self.pop()
|
var right = self.pop()
|
||||||
var left = self.pop()
|
var left = self.pop()
|
||||||
self.push(self.getBoolean(system.`==`(left, right)))
|
self.push(self.getBoolean(system.`==`(left, right)))
|
||||||
|
@ -727,7 +753,7 @@ proc freeVM*(self: VM) =
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
proc initCache(self: VM) =
|
proc initCache(self: var VM) =
|
||||||
## Initializes the static cache for singletons
|
## Initializes the static cache for singletons
|
||||||
## such as true and false
|
## such as true and false
|
||||||
|
|
||||||
|
@ -754,7 +780,7 @@ proc initCache(self: VM) =
|
||||||
self.cached[4] = nInf.asObj()
|
self.cached[4] = nInf.asObj()
|
||||||
|
|
||||||
|
|
||||||
proc initStdlib*(vm: VM) =
|
proc initStdlib*(vm: var VM) =
|
||||||
## Initializes the VM's standard library by defining builtin
|
## Initializes the VM's standard library by defining builtin
|
||||||
## functions that do not require imports. An arity of -1
|
## functions that do not require imports. An arity of -1
|
||||||
## means that the function is variadic (or that it can
|
## means that the function is variadic (or that it can
|
||||||
|
@ -784,7 +810,7 @@ proc initVM*(): VM =
|
||||||
## and internal data structures
|
## and internal data structures
|
||||||
when DEBUG_TRACE_VM:
|
when DEBUG_TRACE_VM:
|
||||||
echo &"DEBUG - VM: Initializing the virtual machine, {JAPL_VERSION_STRING}"
|
echo &"DEBUG - VM: Initializing the virtual machine, {JAPL_VERSION_STRING}"
|
||||||
result = VM(globals: initTable[string, ptr Obj]())
|
result = VM(globals: newHashMap[string, ptr Obj]())
|
||||||
result.initStack()
|
result.initStack()
|
||||||
result.initCache()
|
result.initCache()
|
||||||
result.initStdlib()
|
result.initStdlib()
|
||||||
|
@ -795,7 +821,7 @@ proc initVM*(): VM =
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
proc interpret*(self: VM, source: string, file: string): InterpretResult =
|
proc interpret*(self: var VM, source: string, file: string): InterpretResult =
|
||||||
## Interprets a source string containing JAPL code
|
## Interprets a source string containing JAPL code
|
||||||
when DEBUG_TRACE_VM:
|
when DEBUG_TRACE_VM:
|
||||||
echo &"DEBUG - VM: Preparing to run '{file}'"
|
echo &"DEBUG - VM: Preparing to run '{file}'"
|
||||||
|
|
Loading…
Reference in New Issue