import strformat import strutils import bitops import ndstring import ndlist import hashtable import closure import native type NdValue* = uint # 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 # if bit 63 is 1: # 00 -> list # 01 -> table # 10 -> native funct # 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 # you can imagine these now... const tagClosure* = 0x7fff000000000000'u const tagList* = 0xfffc000000000000'u const tagTable* = 0xfffd000000000000'u const tagNative* = 0xfffe000000000000'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 isClosure*(val: NdValue): bool = val.bitand(mask48) == tagClosure template isList*(val: NdValue): bool = val.bitand(mask48) == tagList template isTable*(val: NdValue): bool = val.bitand(mask48) == tagTable template isNative*(val: NdValue): bool = val.bitand(mask48) == tagNative # 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 asClosure*(val: NdValue): Closure[NdValue] = cast[Closure[NdValue]](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 asNative*(val: NdValue): Native = cast[Native](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 fromClosure*(val: Closure[NdValue]): NdValue = cast[uint](val).bitor(tagClosure) template fromList*(val: List[NdValue]): NdValue = cast[uint](val).bitor(tagList) template fromTable*(val: NdTable[NdValue, NdValue]): NdValue = cast[uint](val).bitor(tagTable) template fromNative*(val: Native): NdValue = cast[uint](val).bitor(tagNative) # 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 {(val).uint.toHex()}" elif val.isClosure(): return &"Closure {(val).uint.toHex()}" elif val.isNative(): return &"Native function {(val).uint.toHex()}" elif val.isString(): return $val.asString() elif val.isTable(): return `$`(val.asTable(), val) 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" elif val.isClosure(): "closure" elif val.isNative(): "native" else: "unknown" # 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() & right.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.") if indexInt == len: list.add(newval) else: 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