preserves-nim/src/preserves/private/encoding.nim

121 lines
3.4 KiB
Nim

# SPDX-FileCopyrightText: ☭ Emery Hemingway
# SPDX-License-Identifier: Unlicense
import std/[endians, streams]
import bigints
import ./values
proc writeVarint(s: Stream; n: Natural) =
var n = n
while n > 0x7f:
s.write(uint8 n or 0x80)
n = n shr 7
s.write(uint8 n and 0x7f)
proc write*[E](str: Stream; pr: Preserve[E]) =
## Write the binary-encoding of a Preserves value to a stream.
if pr.embedded: str.write(0x86'u8)
case pr.kind:
of pkBoolean:
case pr.bool
of false: str.write(0x80'u8)
of true: str.write(0x81'u8)
of pkFloat:
str.write("\x87\x04")
when system.cpuEndian == bigEndian:
str.write(pr.float)
else:
var be: float32
swapEndian32(be.addr, pr.float.unsafeAddr)
str.write(be)
of pkDouble:
str.write("\x87\x08")
when system.cpuEndian == bigEndian:
str.write(pr.double)
else:
var be: float64
swapEndian64(be.addr, pr.double.unsafeAddr)
str.write(be)
of pkRegister:
if pr.register == 0: str.write("\xb0\x00")
else:
const bufLen = sizeof(int)
var buf: array[bufLen, byte]
when bufLen == 4: bigEndian32(addr buf[0], addr pr.register)
elif bufLen == 8: bigEndian64(addr buf[0], addr pr.register)
else: {.error: "int size " & $bufLen & " not supported here".}
if buf[0] != 0x00 and buf[0] != 0xff:
str.write(cast[string](buf)) # dumbass hex conversion
else:
var start = 0
while start < buf.high and buf[0] == buf[succ start]: inc start
if start < buf.high and (buf[succ start] and 0x80) == (buf[0] and 0x80): inc start
str.write('\xb0')
str.write(uint8(bufLen - start))
str.write(cast[string](buf[start..<bufLen]))
of pkBigInt:
if pr.bigint.isZero: str.write("\xb0\x00")
elif pr.bigint.isNegative:
var buf = pr.bigint.succ.toBytes(bigEndian)
for i, b in buf: buf[i] = not b
str.write('\xb0')
if (buf[0] and 0x80) != 0x80:
str.writeVarint(buf.len.succ)
str.write('\xff')
else:
str.writeVarint(buf.len)
str.write(cast[string](buf))
else:
var buf = pr.bigint.toBytes(bigEndian)
str.write('\xb0')
if (buf[0] and 0x80) != 0:
str.writeVarint(buf.len.succ)
str.write('\x00')
else:
str.writeVarint(buf.len)
str.write(cast[string](buf))
of pkString:
str.write(0xb1'u8)
str.writeVarint(pr.string.len)
str.write(pr.string)
of pkByteString:
str.write(0xb2'u8)
str.writeVarint(pr.bytes.len)
str.write(cast[string](pr.bytes))
of pkSymbol:
str.write(0xb3'u8)
str.writeVarint(pr.symbol.len)
str.write(string pr.symbol)
of pkRecord:
assert(pr.record.len > 0)
str.write(0xb4'u8)
str.write(pr.record[pr.record.high])
for i in 0..<pr.record.high:
str.write(pr.record[i])
str.write(0x84'u8)
of pkSequence:
str.write(0xb5'u8)
for e in pr.sequence:
str.write(e)
str.write(0x84'u8)
of pkSet:
str.write(0xb6'u8)
for val in pr.set.items:
str.write(val)
str.write(0x84'u8)
of pkDictionary:
str.write(0xb7'u8)
for (key, value) in pr.dict.items:
str.write(key)
str.write(value)
str.write(0x84'u8)
of pkEmbedded:
str.write(0x86'u8)
str.write(pr.embed.toPreserve)
proc encode*[E](pr: Preserve[E]): seq[byte] =
## Return the binary-encoding of a Preserves value.
let s = newStringStream()
s.write pr
result = cast[seq[byte]](move s.data)