nondescript/types/value.nim

199 lines
5.2 KiB
Nim

import strformat
import bitops
import ndstring
import stringutils
type
NdValue* = uint
NatReturn* = object
ok*: bool
msg*: string
# NaN boxing constants
# see https://craftinginterpreters.com/optimization.html
# bit layout:
# bit 63 is unused (can be used for more types in the future)
# bits 62-50 are all 1 if it's not a float
# bits 49-48 determine type:
# 00 -> nil or bool (singletons)
# 01 -> string
# 10 -> funct
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 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
# 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 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)
# 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()
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"
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
# 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