nondescript/src/ndspkg/types/value.nim

324 lines
9.1 KiB
Nim
Raw Normal View History

2022-01-20 21:54:11 +01:00
import strformat
import strutils
2022-01-29 05:12:33 +01:00
import bitops
import ndstring
2022-02-03 03:18:11 +01:00
import ndlist
import hashtable
2022-02-05 12:30:07 +01:00
import closure
2022-02-07 05:35:07 +01:00
import native
2022-01-20 21:54:11 +01:00
type
2022-01-29 05:12:33 +01:00
NdValue* = uint
2022-01-20 21:54:11 +01:00
2022-01-29 05:12:33 +01:00
# NaN boxing constants
2022-01-20 21:54:11 +01:00
2022-01-29 05:12:33 +01:00
# see https://craftinginterpreters.com/optimization.html
# bit layout:
2022-02-03 03:18:11 +01:00
# bit 63 is used for type information as well
2022-01-29 05:12:33 +01:00
# bits 62-50 are all 1 if it's not a float
# bits 49-48 determine type:
2022-02-03 03:18:11 +01:00
# if bit 63 is 0:
2022-01-29 05:12:33 +01:00
# 00 -> nil or bool (singletons)
# 01 -> string
# 10 -> funct
2022-02-05 12:30:07 +01:00
# 11 -> closure
2022-02-03 03:18:11 +01:00
# if bit 63 is 1:
# 00 -> list
# 01 -> table
2022-02-07 05:35:07 +01:00
# 10 -> native funct
2022-02-03 03:18:11 +01:00
# 11 -> unused for now
2022-01-20 21:54:11 +01:00
2022-01-29 05:12:33 +01:00
const qNan* = 0x7ffc000000000000'u
2022-01-20 21:54:11 +01:00
2022-01-29 05:12:33 +01:00
const ndNil* = 0x7ffc000000000001'u
const ndTrue* = 0x7ffc000000000002'u
const ndFalse* = 0x7ffc000000000003'u
2022-01-20 21:54:11 +01:00
2022-01-29 05:12:33 +01:00
# 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
2022-02-07 05:35:07 +01:00
# you can imagine these now...
2022-02-05 12:30:07 +01:00
const tagClosure* = 0x7fff000000000000'u
2022-02-03 03:18:11 +01:00
const tagList* = 0xfffc000000000000'u
const tagTable* = 0xfffd000000000000'u
2022-02-07 05:35:07 +01:00
const tagNative* = 0xfffe000000000000'u
2022-02-03 03:18:11 +01:00
2022-01-29 05:12:33 +01:00
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
2022-01-20 21:54:11 +01:00
2022-02-05 12:30:07 +01:00
template isClosure*(val: NdValue): bool =
val.bitand(mask48) == tagClosure
2022-02-03 03:18:11 +01:00
template isList*(val: NdValue): bool =
val.bitand(mask48) == tagList
template isTable*(val: NdValue): bool =
val.bitand(mask48) == tagTable
2022-02-07 05:35:07 +01:00
template isNative*(val: NdValue): bool =
val.bitand(mask48) == tagNative
2022-01-29 05:12:33 +01:00
# these assume that the type has been previously determined
template asBool*(val: NdValue): bool =
val == ndTrue
2022-01-20 21:54:11 +01:00
2022-01-29 05:12:33 +01:00
template asFloat*(val: NdValue): float =
cast[float64](val)
2022-01-20 21:54:11 +01:00
2022-01-29 05:12:33 +01:00
template asString*(val: NdValue): NdString =
cast[NdString](val.bitand(mask48.bitnot()))
2022-01-20 21:54:11 +01:00
2022-01-29 20:43:13 +01:00
template asFunct*(val: NdValue): ptr uint8 =
cast[ptr uint8](val.bitand(mask48.bitnot()))
2022-01-20 21:54:11 +01:00
2022-02-05 12:30:07 +01:00
template asClosure*(val: NdValue): Closure[NdValue] =
cast[Closure[NdValue]](val.bitand(mask48.bitnot()))
2022-02-03 03:18:11 +01:00
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()))
2022-02-07 05:35:07 +01:00
template asNative*(val: NdValue): Native =
cast[Native](val.bitand(mask48.bitnot()))
2022-01-29 05:12:33 +01:00
template fromNil*(): NdValue =
ndNil
2022-01-21 01:51:55 +01:00
2022-01-29 05:12:33 +01:00
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))
2022-01-29 20:43:13 +01:00
template fromFunct*(val: ptr uint8): NdValue =
2022-01-29 05:12:33 +01:00
cast[uint](val).bitor(tagFunct)
2022-02-05 12:30:07 +01:00
template fromClosure*(val: Closure[NdValue]): NdValue =
cast[uint](val).bitor(tagClosure)
2022-02-03 03:18:11 +01:00
template fromList*(val: List[NdValue]): NdValue =
cast[uint](val).bitor(tagList)
template fromTable*(val: NdTable[NdValue, NdValue]): NdValue =
cast[uint](val).bitor(tagTable)
2022-02-07 05:35:07 +01:00
template fromNative*(val: Native): NdValue =
cast[uint](val).bitor(tagNative)
2022-02-03 03:18:11 +01:00
# 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
2022-01-29 05:12:33 +01:00
# 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():
2022-02-07 06:03:47 +01:00
return &"Function {(val).uint.toHex()}"
elif val.isClosure():
2022-02-07 06:03:47 +01:00
return &"Closure {(val).uint.toHex()}"
elif val.isNative():
return &"Native function {(val).uint.toHex()}"
2022-01-29 05:12:33 +01:00
elif val.isString():
return $val.asString()
2022-02-03 03:18:11 +01:00
elif val.isTable():
2022-02-07 06:03:47 +01:00
return `$`(val.asTable(), val)
2022-02-03 03:18:11 +01:00
elif val.isList():
return $val.asList()
2022-01-29 05:12:33 +01:00
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"
2022-02-03 03:18:11 +01:00
elif val.isTable():
"table"
elif val.isList():
"list"
2022-02-07 06:03:47 +01:00
elif val.isClosure():
"closure"
elif val.isNative():
"native"
2022-01-29 05:12:33 +01:00
else:
"unknown"
2022-01-20 21:54:11 +01:00
# OPERATIONS
2022-01-27 05:37:10 +01:00
proc negate*(val: var NdValue): NatReturn {.inline.} =
2022-01-29 05:12:33 +01:00
if not val.isFloat():
return natError("Operand must be a number.")
2022-01-20 21:54:11 +01:00
else:
2022-01-29 05:12:33 +01:00
val = fromFloat(-val.asFloat())
return natOk
2022-01-20 21:54:11 +01:00
2022-01-27 05:37:10 +01:00
proc add*(val: var NdValue, right: NdValue): NatReturn {.inline.} =
2022-01-29 05:12:33 +01:00
if val.isFloat() and right.isFloat():
val = fromFloat(val.asFloat() + right.asFloat())
elif val.isString() and right.isString():
2022-02-06 01:02:22 +01:00
val = fromNdString(val.asString() & right.asString())
2022-01-20 21:54:11 +01:00
else:
2022-01-29 05:12:33 +01:00
return natError(&"Attempt to add types {val.friendlyType()} and {right.friendlyType()}.")
return natOk
2022-01-27 05:37:10 +01:00
proc subtract*(val: var NdValue, right: NdValue): NatReturn {.inline.} =
2022-01-29 05:12:33 +01:00
if val.isFloat() and right.isFloat():
val = fromFloat(val.asFloat() - right.asFloat())
2022-01-20 21:54:11 +01:00
else:
2022-01-29 05:12:33 +01:00
return natError(&"Attempt to subtract types {val.friendlyType()} and {right.friendlyType()}.")
return natOk
2022-01-20 21:54:11 +01:00
2022-01-27 05:37:10 +01:00
proc multiply*(val: var NdValue, right: NdValue): NatReturn {.inline.} =
2022-01-29 05:12:33 +01:00
if val.isFloat() and right.isFloat():
val = fromFloat(val.asFloat() * right.asFloat())
2022-01-20 21:54:11 +01:00
else:
2022-01-29 05:12:33 +01:00
return natError(&"Attempt to multiply types {val.friendlyType()} and {right.friendlyType()}.")
return natOk
2022-01-20 21:54:11 +01:00
2022-01-27 05:37:10 +01:00
proc divide*(val: var NdValue, right: NdValue): NatReturn {.inline.} =
2022-01-29 05:12:33 +01:00
if val.isFloat() and right.isFloat():
val = fromFloat(val.asFloat() / right.asFloat())
2022-01-20 21:54:11 +01:00
else:
2022-01-29 05:12:33 +01:00
return natError(&"Attempt to divide types {val.friendlyType()} and {right.friendlyType()}.")
return natOk
2022-01-20 21:54:11 +01:00
proc less*(val: var NdValue, right: NdValue): NatReturn {.inline.} =
2022-01-29 05:12:33 +01:00
if val.isFloat() and right.isFloat():
val = fromBool(val.asFloat() < right.asFloat())
2022-01-20 21:54:11 +01:00
else:
2022-01-29 05:12:33 +01:00
return natError(&"Attempt to compare types {val.friendlyType()} and {right.friendlyType()}.")
return natOk
2022-01-20 21:54:11 +01:00
proc greater*(val: var NdValue, right: NdValue): NatReturn {.inline.} =
2022-01-29 05:12:33 +01:00
if val.isFloat() and right.isFloat():
val = fromBool(val.asFloat() > right.asFloat())
2022-01-20 21:54:11 +01:00
else:
2022-01-29 05:12:33 +01:00
return natError(&"Attempt to compare types {val.friendlyType()} and {right.friendlyType()}.")
return natOk
2022-02-03 03:18:11 +01:00
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
2022-02-03 03:18:11 +01:00
proc getIndex*(val: var NdValue, index: NdValue): NatReturn {.inline.} =
if val.isTable():
var tbl = val.asTable()
if not tbl[].tableGet(index, val):
2022-02-03 03:18:11 +01:00
val = fromNil()
return natOk
2022-02-03 03:18:11 +01:00
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:
2022-02-03 03:18:11 +01:00
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)
2022-02-03 03:18:11 +01:00
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:
2022-02-03 03:18:11 +01:00
return natError(&"Index out of bounds. Index used: {index}; valid range: 0 - {len - 1} inclusive.")
if indexInt == len:
list.add(newval)
else:
list.setIndex(indexInt, newval)
2022-02-03 03:18:11 +01:00
else:
return natError(&"Attempt to set-index a type {val.friendlyType()}, this operator is only available for lists and tables.")
return natOk