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