nondescript/src/ndspkg/types/value.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