302 lines
8.4 KiB
Nim
302 lines
8.4 KiB
Nim
import strformat
|
|
import bitops
|
|
|
|
import ndstring
|
|
import stringutils
|
|
import ndtable
|
|
import ndlist
|
|
|
|
type
|
|
NdValue* = uint
|
|
|
|
NatReturn* = object
|
|
ok*: bool
|
|
msg*: string
|
|
|
|
# NaN boxing constants
|
|
|
|
# see https://craftinginterpreters.com/optimization.html
|
|
# bit layout:
|
|
# bit 63 is used for type information as well
|
|
# bits 62-50 are all 1 if it's not a float
|
|
# bits 49-48 determine type:
|
|
# if bit 63 is 0:
|
|
# 00 -> nil or bool (singletons)
|
|
# 01 -> string
|
|
# 10 -> funct
|
|
# 11 -> closure (to be implemented later)
|
|
# if bit 63 is 1:
|
|
# 00 -> list
|
|
# 01 -> table
|
|
# 10 -> native funct (to be implemented later)
|
|
# 11 -> unused for now
|
|
|
|
const qNan* = 0x7ffc000000000000'u
|
|
|
|
const ndNil* = 0x7ffc000000000001'u
|
|
const ndTrue* = 0x7ffc000000000002'u
|
|
const ndFalse* = 0x7ffc000000000003'u
|
|
|
|
# 0111 1111 1111 11*01* 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
|
|
const tagString* = 0x7ffd000000000000'u
|
|
|
|
# 0111 1111 1111 11*10* 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
|
|
const tagFunct* = 0x7ffe000000000000'u
|
|
|
|
const tagList* = 0xfffc000000000000'u
|
|
const tagTable* = 0xfffd000000000000'u
|
|
|
|
const mask48* = 0xffff000000000000'u
|
|
|
|
# converters
|
|
|
|
template isNil*(val: NdValue): bool =
|
|
val == ndNil
|
|
|
|
template isBool*(val: NdValue): bool =
|
|
(val.bitor(1'u)) == ndFalse
|
|
|
|
template isFloat*(val: NdValue): bool =
|
|
val.bitand(qNan) != qNan
|
|
|
|
template isString*(val: NdValue): bool =
|
|
val.bitand(mask48) == tagString
|
|
|
|
template isFunct*(val: NdValue): bool =
|
|
val.bitand(mask48) == tagFunct
|
|
|
|
template isList*(val: NdValue): bool =
|
|
val.bitand(mask48) == tagList
|
|
|
|
template isTable*(val: NdValue): bool =
|
|
val.bitand(mask48) == tagTable
|
|
|
|
# these assume that the type has been previously determined
|
|
template asBool*(val: NdValue): bool =
|
|
val == ndTrue
|
|
|
|
template asFloat*(val: NdValue): float =
|
|
cast[float64](val)
|
|
|
|
template asString*(val: NdValue): NdString =
|
|
cast[NdString](val.bitand(mask48.bitnot()))
|
|
|
|
template asFunct*(val: NdValue): ptr uint8 =
|
|
cast[ptr uint8](val.bitand(mask48.bitnot()))
|
|
|
|
template asList*(val: NdValue): List[NdValue] =
|
|
cast[List[NdValue]](val.bitand(mask48.bitnot()))
|
|
|
|
template asTable*(val: NdValue): NdTable[NdValue, NdValue] =
|
|
cast[NdTable[NdValue, NdValue]](val.bitand(mask48.bitnot()))
|
|
|
|
template fromNil*(): NdValue =
|
|
ndNil
|
|
|
|
template fromBool*(val: bool): NdValue =
|
|
if val: ndTrue else: ndFalse
|
|
|
|
template fromFloat*(val: float): NdValue =
|
|
cast[NdValue](val)
|
|
|
|
template fromNdString*(val: NdString): NdValue =
|
|
cast[uint](val).bitor(tagString)
|
|
|
|
template fromNimString*(sval: string): NdValue =
|
|
fromNdString(newString(sval))
|
|
|
|
template fromFunct*(val: ptr uint8): NdValue =
|
|
cast[uint](val).bitor(tagFunct)
|
|
|
|
template fromList*(val: List[NdValue]): NdValue =
|
|
cast[uint](val).bitor(tagList)
|
|
|
|
template fromTable*(val: NdTable[NdValue, NdValue]): NdValue =
|
|
cast[uint](val).bitor(tagTable)
|
|
|
|
|
|
# for hashtables
|
|
|
|
proc fnv1a*(ndval: NdValue): int =
|
|
var hash = 2166136261'u32
|
|
var val = ndval
|
|
for i in countup(0, 7):
|
|
hash = hash xor val.uint8.uint32
|
|
hash *= 16777619
|
|
val = val shr 8
|
|
return hash.int
|
|
|
|
proc equal*(val, right: NdValue): bool =
|
|
if val.isFloat() and right.isFloat():
|
|
val.asFloat() == right.asFloat()
|
|
else:
|
|
val == right
|
|
|
|
# KON VALUE HELPERS, MUST BE DEFINED FOR EVERY KONVALUE
|
|
|
|
proc `$`*(val: NdValue): string =
|
|
if val.isNil():
|
|
return "nil"
|
|
elif val == ndTrue:
|
|
return "true"
|
|
elif val == ndFalse:
|
|
return "false"
|
|
elif val.isFloat():
|
|
return $val.asFloat()
|
|
elif val.isFunct():
|
|
return &"Function object {cast[uint](val.asFunct())}"
|
|
elif val.isString():
|
|
return $val.asString()
|
|
elif val.isTable():
|
|
return $val.asTable()
|
|
elif val.isList():
|
|
return $val.asList()
|
|
|
|
proc isFalsey*(val: NdValue): bool =
|
|
val == ndNil or val == ndFalse
|
|
|
|
template isTruthy*(val: NdValue): bool =
|
|
not val.isFalsey()
|
|
|
|
proc friendlyType*(val: NdValue): string =
|
|
if val == ndNil:
|
|
"nil"
|
|
elif val.isBool():
|
|
"bool"
|
|
elif val.isFloat():
|
|
"number"
|
|
elif val.isString():
|
|
"string"
|
|
elif val.isFunct():
|
|
"function"
|
|
elif val.isTable():
|
|
"table"
|
|
elif val.isList():
|
|
"list"
|
|
else:
|
|
"unknown"
|
|
|
|
# NatReturn misc
|
|
|
|
proc natError*(msg: string): NatReturn {.inline.} =
|
|
NatReturn(ok: false, msg: msg)
|
|
|
|
const natOk* = NatReturn(ok: true)
|
|
|
|
# OPERATIONS
|
|
|
|
proc negate*(val: var NdValue): NatReturn {.inline.} =
|
|
if not val.isFloat():
|
|
return natError("Operand must be a number.")
|
|
else:
|
|
val = fromFloat(-val.asFloat())
|
|
return natOk
|
|
|
|
proc add*(val: var NdValue, right: NdValue): NatReturn {.inline.} =
|
|
if val.isFloat() and right.isFloat():
|
|
val = fromFloat(val.asFloat() + right.asFloat())
|
|
elif val.isString() and right.isString():
|
|
val = fromNdString(val.asString() & val.asString())
|
|
else:
|
|
return natError(&"Attempt to add types {val.friendlyType()} and {right.friendlyType()}.")
|
|
return natOk
|
|
|
|
proc subtract*(val: var NdValue, right: NdValue): NatReturn {.inline.} =
|
|
if val.isFloat() and right.isFloat():
|
|
val = fromFloat(val.asFloat() - right.asFloat())
|
|
else:
|
|
return natError(&"Attempt to subtract types {val.friendlyType()} and {right.friendlyType()}.")
|
|
return natOk
|
|
|
|
proc multiply*(val: var NdValue, right: NdValue): NatReturn {.inline.} =
|
|
if val.isFloat() and right.isFloat():
|
|
val = fromFloat(val.asFloat() * right.asFloat())
|
|
else:
|
|
return natError(&"Attempt to multiply types {val.friendlyType()} and {right.friendlyType()}.")
|
|
return natOk
|
|
|
|
proc divide*(val: var NdValue, right: NdValue): NatReturn {.inline.} =
|
|
if val.isFloat() and right.isFloat():
|
|
val = fromFloat(val.asFloat() / right.asFloat())
|
|
else:
|
|
return natError(&"Attempt to divide types {val.friendlyType()} and {right.friendlyType()}.")
|
|
return natOk
|
|
|
|
proc less*(val: var NdValue, right: NdValue): NatReturn {.inline.} =
|
|
if val.isFloat() and right.isFloat():
|
|
val = fromBool(val.asFloat() < right.asFloat())
|
|
else:
|
|
return natError(&"Attempt to compare types {val.friendlyType()} and {right.friendlyType()}.")
|
|
return natOk
|
|
|
|
proc greater*(val: var NdValue, right: NdValue): NatReturn {.inline.} =
|
|
if val.isFloat() and right.isFloat():
|
|
val = fromBool(val.asFloat() > right.asFloat())
|
|
else:
|
|
return natError(&"Attempt to compare types {val.friendlyType()} and {right.friendlyType()}.")
|
|
return natOk
|
|
|
|
proc getLength*(val: var NdValue): NatReturn {.inline.} =
|
|
if val.isList():
|
|
val = fromFloat(val.asList().getLength().float)
|
|
elif val.isTable():
|
|
val = fromFloat(val.asTable().getLength().float)
|
|
elif val.isString():
|
|
val = fromFloat(val.asString().getLength().float)
|
|
else:
|
|
return natError(&"Attempt to get length of type {val.friendlyType()}, this operator is only available for lists, tables and strings.")
|
|
return natOk
|
|
|
|
proc getIndex*(val: var NdValue, index: NdValue): NatReturn {.inline.} =
|
|
if val.isTable():
|
|
var tbl = val.asTable()
|
|
if not tbl.tableGet(index, val):
|
|
val = fromNil()
|
|
return natOk
|
|
|
|
template indexInt: int =
|
|
index.asFloat().int
|
|
|
|
template checkBounds(len: int) =
|
|
if not index.isFloat():
|
|
return natError(&"Attempt to index using an index of type {index.friendlyType()}: only numerical indexes are allowed.")
|
|
|
|
if indexInt() < 0 or indexInt() >= len:
|
|
return natError(&"Index out of bounds. Index used: {index}; valid range: 0 - {len - 1} inclusive.")
|
|
|
|
if val.isList():
|
|
var list = val.asList()
|
|
let len = list.getLength()
|
|
len.checkBounds()
|
|
val = list.getIndex(indexInt)
|
|
elif val.isString():
|
|
var str = val.asString()
|
|
let len = str.getLength()
|
|
len.checkBounds()
|
|
val = str.getIndex(indexInt).fromNdString()
|
|
else:
|
|
return natError(&"Attempt to index a type {val.friendlyType()}, this operator is only available for lists, tables and strings.")
|
|
return natOk
|
|
|
|
proc setIndex*(val: var NdValue, index: NdValue, newval: NdValue): NatReturn {.inline.} =
|
|
if val.isTable():
|
|
var tbl = val.asTable()
|
|
discard tbl.tableSet(index, newval)
|
|
return natOk
|
|
|
|
if val.isList():
|
|
var list = val.asList()
|
|
let len = list.getLength()
|
|
let indexint = index.asFloat().int
|
|
if not index.isFloat():
|
|
return natError(&"Attempt to index using an index of type {index.friendlyType()}: only numerical indexes are allowed.")
|
|
|
|
if indexInt < 0 or indexInt >= len:
|
|
return natError(&"Index out of bounds. Index used: {index}; valid range: 0 - {len - 1} inclusive.")
|
|
list.setIndex(indexInt, newval)
|
|
else:
|
|
return natError(&"Attempt to set-index a type {val.friendlyType()}, this operator is only available for lists and tables.")
|
|
return natOk
|
|
|
|
|