Move some of preserves implementation to preserves/private
This commit is contained in:
parent
126365d164
commit
adadcc181a
|
@ -1,12 +1,14 @@
|
|||
# SPDX-FileCopyrightText: ☭ Emery Hemingway
|
||||
# SPDX-License-Identifier: Unlicense
|
||||
|
||||
import std/[base64, endians, hashes, options, sets, sequtils, streams, strutils, tables, typetraits]
|
||||
import std/[options, sets, sequtils, strutils, tables, typetraits]
|
||||
import ./preserves/private/macros
|
||||
|
||||
from std/algorithm import sort
|
||||
from std/json import escapeJson, escapeJsonUnquoted
|
||||
import ./preserves/private/dot
|
||||
import ./preserves/private/[encoding, decoding, dot, parsing, texts, values]
|
||||
|
||||
export encoding, decoding, parsing, texts, values
|
||||
|
||||
when defined(tracePreserves):
|
||||
when defined(posix):
|
||||
|
@ -16,194 +18,6 @@ when defined(tracePreserves):
|
|||
else:
|
||||
template trace(args: varargs[untyped]) = discard
|
||||
|
||||
type
|
||||
PreserveKind* = enum
|
||||
pkBoolean, pkFloat, pkDouble, pkSignedInteger, pkString, pkByteString, pkSymbol,
|
||||
pkRecord, pkSequence, pkSet, pkDictionary, pkEmbedded
|
||||
|
||||
const
|
||||
atomKinds* = {pkBoolean, pkFloat, pkDouble, pkSignedInteger, pkString, pkByteString, pkSymbol}
|
||||
compoundKinds* = {pkRecord, pkSequence, pkSet, pkDictionary}
|
||||
|
||||
type Symbol* = distinct string
|
||||
proc `<`*(x, y: Symbol): bool {.borrow.}
|
||||
proc `==`*(x, y: Symbol): bool {.borrow.}
|
||||
proc hash*(s: Symbol): Hash {.borrow.}
|
||||
proc len*(s: Symbol): int {.borrow.}
|
||||
|
||||
proc `$`*(s: Symbol): string =
|
||||
let sym = string s
|
||||
if sym.len > 0 and sym[0] in {'A'..'z'} and not sym.anyIt(char(it) in { '\x00'..'\x19', '"', '\\', '|' }):
|
||||
result = sym
|
||||
else:
|
||||
result = newStringOfCap(sym.len shl 1)
|
||||
result.add('|')
|
||||
for c in sym:
|
||||
case c
|
||||
of '\\':
|
||||
result.add("\\\\")
|
||||
of '/':
|
||||
result.add("\\/")
|
||||
of '\x08':
|
||||
result.add("\\b")
|
||||
of '\x0c':
|
||||
result.add("\\f")
|
||||
of '\x0a':
|
||||
result.add("\\n")
|
||||
of '\x0d':
|
||||
result.add("\\r")
|
||||
of '\x09':
|
||||
result.add("\\t")
|
||||
of '|':
|
||||
result.add("\\|")
|
||||
else:
|
||||
result.add(c)
|
||||
result.add('|')
|
||||
|
||||
type
|
||||
Preserve*[E] = object
|
||||
embedded*: bool
|
||||
## Flag to mark embedded Preserves
|
||||
case kind*: PreserveKind
|
||||
of pkBoolean:
|
||||
bool*: bool
|
||||
of pkFloat:
|
||||
float*: float32
|
||||
of pkDouble:
|
||||
double*: float64
|
||||
of pkSignedInteger:
|
||||
int*: BiggestInt
|
||||
of pkString:
|
||||
string*: string
|
||||
of pkByteString:
|
||||
bytes*: seq[byte]
|
||||
of pkSymbol:
|
||||
symbol*: Symbol
|
||||
of pkRecord:
|
||||
record*: seq[Preserve[E]] # label is last
|
||||
of pkSequence:
|
||||
sequence*: seq[Preserve[E]]
|
||||
of pkSet:
|
||||
set*: seq[Preserve[E]]
|
||||
# TODO: HashSet
|
||||
of pkDictionary:
|
||||
dict*: seq[DictEntry[E]]
|
||||
# TODO: Tables
|
||||
of pkEmbedded:
|
||||
embed*: E
|
||||
|
||||
DictEntry[E] = tuple[key: Preserve[E], val: Preserve[E]]
|
||||
|
||||
func `==`*[A, B](x: Preserve[A]; y: Preserve[B]): bool =
|
||||
## Check `x` and `y` for equivalence.
|
||||
if x.kind == y.kind and x.embedded == y.embedded:
|
||||
case x.kind
|
||||
of pkBoolean:
|
||||
result = x.bool == y.bool
|
||||
of pkFloat:
|
||||
result = x.float == y.float
|
||||
of pkDouble:
|
||||
result = x.double == y.double
|
||||
of pkSignedInteger:
|
||||
result = x.int == y.int
|
||||
of pkString:
|
||||
result = x.string == y.string
|
||||
of pkByteString:
|
||||
result = x.bytes == y.bytes
|
||||
of pkSymbol:
|
||||
result = x.symbol == y.symbol
|
||||
of pkRecord:
|
||||
result = x.record.len == y.record.len
|
||||
for i in 0..x.record.high:
|
||||
if not result: break
|
||||
result = result and (x.record[i] == y.record[i])
|
||||
of pkSequence:
|
||||
for i, val in x.sequence:
|
||||
if y.sequence[i] != val: return false
|
||||
result = true
|
||||
of pkSet:
|
||||
result = x.set.len == y.set.len
|
||||
for i in 0..x.set.high:
|
||||
if not result: break
|
||||
result = result and (x.set[i] == y.set[i])
|
||||
of pkDictionary:
|
||||
result = x.dict.len == y.dict.len
|
||||
for i in 0..x.dict.high:
|
||||
if not result: break
|
||||
result = result and
|
||||
(x.dict[i].key == y.dict[i].key) and
|
||||
(x.dict[i].val == y.dict[i].val)
|
||||
of pkEmbedded:
|
||||
when A is B:
|
||||
when A is void:
|
||||
result = true
|
||||
else:
|
||||
result = x.embed == y.embed
|
||||
|
||||
proc `<`(x, y: string | seq[byte]): bool =
|
||||
for i in 0 .. min(x.high, y.high):
|
||||
if x[i] < y[i]: return true
|
||||
if x[i] != y[i]: return false
|
||||
x.len < y.len
|
||||
|
||||
proc `<`*[A, B](x: Preserve[A]; y: Preserve[B]): bool =
|
||||
## Preserves have a total order over values. Check if `x` is ordered before `y`.
|
||||
if x.embedded != y.embedded:
|
||||
result = y.embedded
|
||||
elif x.kind != y.kind:
|
||||
result = x.kind < y.kind
|
||||
else:
|
||||
case x.kind
|
||||
of pkBoolean:
|
||||
result = (not x.bool) and y.bool
|
||||
of pkFloat:
|
||||
result = x.float < y.float
|
||||
of pkDouble:
|
||||
result = x.double < y.double
|
||||
of pkSignedInteger:
|
||||
result = x.int < y.int
|
||||
of pkString:
|
||||
result = x.string < y.string
|
||||
of pkByteString:
|
||||
result = x.bytes < y.bytes
|
||||
of pkSymbol:
|
||||
result = x.symbol < y.symbol
|
||||
of pkRecord:
|
||||
if x.record[x.record.high] < y.record[y.record.high]: return true
|
||||
for i in 0..<min(x.record.high, y.record.high):
|
||||
if x.record[i] < y.record[i]: return true
|
||||
if x.record[i] == y.record[i]: return false
|
||||
result = x.record.len < y.record.len
|
||||
of pkSequence:
|
||||
for i in 0..min(x.sequence.high, y.sequence.high):
|
||||
if x.sequence[i] < y.sequence[i]: return true
|
||||
if x.sequence[i] != y.sequence[i]: return false
|
||||
result = x.sequence.len < y.sequence.len
|
||||
of pkSet:
|
||||
for i in 0..min(x.set.high, y.set.high):
|
||||
if x.set[i] < y.set[i]: return true
|
||||
if x.set[i] != y.set[i]: return false
|
||||
result = x.set.len < y.set.len
|
||||
of pkDictionary:
|
||||
for i in 0..min(x.dict.high, y.dict.high):
|
||||
if x.dict[i].key < y.dict[i].key: return true
|
||||
if x.dict[i].key == y.dict[i].key:
|
||||
if x.dict[i].val < y.dict[i].val: return true
|
||||
if x.dict[i].val != y.dict[i].val: return false
|
||||
result = x.dict.len < y.dict.len
|
||||
of pkEmbedded:
|
||||
when (not A is void) and (A is B):
|
||||
result = x.embed < y.embed
|
||||
|
||||
func cmp*[E](x, y: Preserve[E]): int =
|
||||
## Compare by Preserves total ordering.
|
||||
if x == y: 0
|
||||
elif x < y: -1
|
||||
else: 1
|
||||
|
||||
proc sort*[E](pr: var Preserve[E]) = sort(pr.sequence, cmp)
|
||||
## Sort a Preserves array by total ordering.
|
||||
|
||||
proc sortDict[E](pr: var Preserve[E]) =
|
||||
sort(pr.dict) do (x, y: DictEntry[E]) -> int:
|
||||
cmp(x.key, y.key)
|
||||
|
@ -223,64 +37,6 @@ proc cannonicalize*[E](pr: var Preserve[E]) =
|
|||
else:
|
||||
discard
|
||||
|
||||
proc hash*[E](pr: Preserve[E]): Hash =
|
||||
## Produce a `Hash` of `pr` for use with a `HashSet` or `Table`.
|
||||
var h = hash(pr.kind.int) !& hash(pr.embedded)
|
||||
case pr.kind
|
||||
of pkBoolean:
|
||||
h = h !& hash(pr.bool)
|
||||
of pkFloat:
|
||||
h = h !& hash(pr.float)
|
||||
of pkDouble:
|
||||
h = h !& hash(pr.double)
|
||||
of pkSignedInteger:
|
||||
h = h !& hash(pr.int)
|
||||
of pkString:
|
||||
h = h !& hash(pr.string)
|
||||
of pkByteString:
|
||||
h = h !& hash(pr.bytes)
|
||||
of pkSymbol:
|
||||
h = h !& hash(string pr.symbol)
|
||||
of pkRecord:
|
||||
for val in pr.record:
|
||||
h = h !& hash(val)
|
||||
of pkSequence:
|
||||
for val in pr.sequence:
|
||||
h = h !& hash(val)
|
||||
of pkSet:
|
||||
for val in pr.set.items:
|
||||
h = h !& hash(val)
|
||||
of pkDictionary:
|
||||
for (key, val) in pr.dict.items:
|
||||
h = h !& hash(key) !& hash(val)
|
||||
of pkEmbedded:
|
||||
when E is void:
|
||||
h = h !& hash(pr.embed)
|
||||
else:
|
||||
if pr.embed.isNil:
|
||||
h = h !& hash(false)
|
||||
else:
|
||||
h = h !& hash(pr.embed)
|
||||
!$h
|
||||
|
||||
proc `[]`*(pr: Preserve; i: int): Preserve =
|
||||
## Select an indexed value from ``pr``.
|
||||
## Only valid for records and sequences.
|
||||
case pr.kind
|
||||
of pkRecord: pr.record[i]
|
||||
of pkSequence: pr.sequence[i]
|
||||
else:
|
||||
raise newException(ValueError, "Preserves value is not indexable")
|
||||
|
||||
proc `[]=`*(pr: var Preserve; i: Natural; val: Preserve) =
|
||||
## Assign an indexed value into ``pr``.
|
||||
## Only valid for records and sequences.
|
||||
case pr.kind
|
||||
of pkRecord: pr.record[i] = val
|
||||
of pkSequence: pr.sequence[i] = val
|
||||
else:
|
||||
raise newException(ValueError, "`Preserves value is not indexable")
|
||||
|
||||
proc getOrDefault(pr: Preserve; key: Preserve): Preserve =
|
||||
## Retrieves the value of `pr[key]` if `pr` is a dictionary containing `key`
|
||||
## or returns the `#f` Preserves value.
|
||||
|
@ -302,21 +58,6 @@ proc pop*(pr: var Preserve; key: Preserve; val: var Preserve): bool =
|
|||
delete(pr.dict, i, i)
|
||||
return true
|
||||
|
||||
proc incl*(pr: var Preserve; key: Preserve) =
|
||||
## Include `key` in the Preserves set `pr`.
|
||||
for i in 0..pr.set.high:
|
||||
if key < pr.set[i]:
|
||||
insert(pr.set, [key], i)
|
||||
return
|
||||
pr.set.add(key)
|
||||
|
||||
proc excl*(pr: var Preserve; key: Preserve) =
|
||||
## Exclude `key` from the Preserves set `pr`.
|
||||
for i in 0..pr.set.high:
|
||||
if pr.set[i] == key:
|
||||
delete(pr.set, i..i)
|
||||
break
|
||||
|
||||
proc `[]`*(pr, key: Preserve): Preserve {.deprecated: "use step instead".} =
|
||||
## Select a value by `key` from `pr`.
|
||||
## Works for sequences, records, and dictionaries.
|
||||
|
@ -362,17 +103,6 @@ func step*[E](pr: Preserve[E]; key: Symbol): Option[Preserve[E]] =
|
|||
result = some(v)
|
||||
break
|
||||
|
||||
proc `[]=`*(pr: var Preserve; key, val: Preserve) =
|
||||
## Insert `val` by `key` in the Preserves dictionary `pr`.
|
||||
for i in 0..pr.dict.high:
|
||||
if key < pr.dict[i].key:
|
||||
insert(pr.dict, [(key, val, )], i)
|
||||
return
|
||||
elif key == pr.dict[i].key:
|
||||
pr.dict[i].val = val
|
||||
return
|
||||
pr.dict.add((key, val, ))
|
||||
|
||||
proc mget*(pr: var Preserve; key: Preserve): var Preserve =
|
||||
## Select a value by `key` from the Preserves dictionary `pr`.
|
||||
if pr.isDictionary:
|
||||
|
@ -551,262 +281,6 @@ proc unembed*[E](pr: Preserve[E]): E =
|
|||
raise newException(ValueError, "not an embedded value")
|
||||
pr.embed
|
||||
|
||||
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 readVarint(s: Stream): uint =
|
||||
var
|
||||
shift = 0
|
||||
c = uint s.readUint8
|
||||
while (c and 0x80) == 0x80:
|
||||
result = result or ((c and 0x7f) shl shift)
|
||||
inc(shift, 7)
|
||||
c = uint s.readUint8
|
||||
result = result or (c shl shift)
|
||||
|
||||
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 pkSignedInteger:
|
||||
if pr.int == 0:
|
||||
str.write("\xb0\x00")
|
||||
else:
|
||||
var bitCount = 1'u8
|
||||
if pr.int < 0:
|
||||
while ((not pr.int) shr bitCount) != 0:
|
||||
inc(bitCount)
|
||||
else:
|
||||
while (pr.int shr bitCount) != 0:
|
||||
inc(bitCount)
|
||||
var byteCount = (bitCount + 8) div 8
|
||||
str.write(0xb0'u8)
|
||||
str.writeVarint(byteCount)
|
||||
proc write(n: uint8; i: BiggestInt) =
|
||||
if n > 1:
|
||||
write(n.pred, i shr 8)
|
||||
str.write(i.uint8)
|
||||
write(byteCount, pr.int)
|
||||
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)
|
||||
|
||||
proc decodePreserves*(s: Stream; E = void): Preserve[E] =
|
||||
## Decode a Preserves value from a binary-encoded stream.
|
||||
if s.atEnd: raise newException(IOError, "End of Preserves stream")
|
||||
const endMarker = 0x84
|
||||
let tag = s.readUint8()
|
||||
case tag
|
||||
of 0x80: result = Preserve[E](kind: pkBoolean, bool: false)
|
||||
of 0x81: result = Preserve[E](kind: pkBoolean, bool: true)
|
||||
of 0x85:
|
||||
discard decodePreserves(s, E)
|
||||
result = decodePreserves(s, E)
|
||||
of 0x86:
|
||||
result = decodePreserves(s, E)
|
||||
result.embedded = true
|
||||
of 0x87:
|
||||
let n = s.readUint8()
|
||||
case n
|
||||
of 4:
|
||||
when system.cpuEndian == bigEndian:
|
||||
result = Preserve[E](kind: pkFloat, float: s.readFloat32())
|
||||
else:
|
||||
result = Preserve[E](kind: pkFloat)
|
||||
var be = s.readFloat32()
|
||||
swapEndian32(result.float.addr, be.addr)
|
||||
of 8:
|
||||
when system.cpuEndian == bigEndian:
|
||||
result = Preserve[E](kind: pkDouble, double: s.readFloat64())
|
||||
else:
|
||||
result = Preserve[E](kind: pkDouble)
|
||||
var be = s.readFloat64()
|
||||
swapEndian64(result.double.addr, be.addr)
|
||||
else:
|
||||
raise newException(IOError, "unhandled IEEE754 value of " & $n & " bytes")
|
||||
of 0xb1:
|
||||
var data = newString(s.readVarint())
|
||||
if data.len > 0:
|
||||
let n = s.readData(unsafeAddr data[0], data.len)
|
||||
if n != data.len:
|
||||
raise newException(IOError, "short read")
|
||||
result = Preserve[E](kind: pkString, string: data)
|
||||
of 0xb2:
|
||||
var data = newSeq[byte](s.readVarint())
|
||||
if data.len > 0:
|
||||
let n = s.readData(addr data[0], data.len)
|
||||
if n != data.len:
|
||||
raise newException(IOError, "short read")
|
||||
result = Preserve[E](kind: pkByteString, bytes: data)
|
||||
of 0xb3:
|
||||
var data = newString(s.readVarint())
|
||||
if data.len > 0:
|
||||
let n = s.readData(addr data[0], data.len)
|
||||
if n != data.len:
|
||||
raise newException(IOError, "short read")
|
||||
result = Preserve[E](kind: pkSymbol, symbol: Symbol data)
|
||||
of 0xb4:
|
||||
result = Preserve[E](kind: pkRecord)
|
||||
var label = decodePreserves(s, E)
|
||||
while s.peekUint8() != endMarker:
|
||||
result.record.add decodePreserves(s, E)
|
||||
result.record.add(move label)
|
||||
discard s.readUint8()
|
||||
of 0xb5:
|
||||
result = Preserve[E](kind: pkSequence)
|
||||
while s.peekUint8() != endMarker:
|
||||
result.sequence.add decodePreserves(s, E)
|
||||
discard s.readUint8()
|
||||
of 0xb6:
|
||||
result = Preserve[E](kind: pkSet)
|
||||
while s.peekUint8() != endMarker:
|
||||
incl(result, decodePreserves(s, E))
|
||||
discard s.readUint8()
|
||||
of 0xb7:
|
||||
result = Preserve[E](kind: pkDictionary)
|
||||
while s.peekUint8() != endMarker:
|
||||
result[decodePreserves(s, E)] = decodePreserves(s, E)
|
||||
discard s.readUint8()
|
||||
of 0xb0:
|
||||
var len = s.readVarint()
|
||||
result = Preserve[E](kind: pkSignedInteger)
|
||||
if len > 0:
|
||||
if (s.peekUint8() and 0x80) == 0x80:
|
||||
result.int = BiggestInt -1
|
||||
while len > 0:
|
||||
result.int = (result.int shl 8) + s.readUint8().BiggestInt
|
||||
dec(len)
|
||||
|
||||
of endMarker:
|
||||
raise newException(ValueError, "invalid Preserves stream")
|
||||
else:
|
||||
raise newException(ValueError, "invalid Preserves tag byte 0x" & tag.toHex(2))
|
||||
|
||||
proc decodePreserves*(s: string; E = void): Preserve[E] =
|
||||
## Decode a string of binary-encoded Preserves.
|
||||
decodePreserves(s.newStringStream, E)
|
||||
|
||||
proc decodePreserves*(s: seq[byte]; E = void): Preserve[E] =
|
||||
## Decode a byte-string of binary-encoded Preserves.
|
||||
decodePreserves(cast[string](s), E)
|
||||
|
||||
type BufferedDecoder* = object
|
||||
## Type for buffering binary Preserves before decoding.
|
||||
stream: StringStream
|
||||
appendPosition, decodePosition, maxSize: int
|
||||
|
||||
proc newBufferedDecoder*(maxSize = 4096): BufferedDecoder =
|
||||
## Create a new `newBufferedDecoder`.
|
||||
runnableExamples:
|
||||
var
|
||||
buf = newBufferedDecoder()
|
||||
bin = encode(parsePreserves("<foobar>"))
|
||||
buf.feed(bin[0..2])
|
||||
buf.feed(bin[3..bin.high])
|
||||
var (success, pr) = decode(buf)
|
||||
assert success
|
||||
assert $pr == "<foobar>"
|
||||
BufferedDecoder(
|
||||
stream: newStringStream(newStringOfCap(maxSize)),
|
||||
maxSize: maxSize,
|
||||
)
|
||||
|
||||
proc feed*(dec: var BufferedDecoder; buf: pointer; len: int) =
|
||||
assert len > 0
|
||||
if dec.maxSize > 0 and dec.maxSize < (dec.appendPosition + len):
|
||||
raise newException(IOError, "BufferedDecoder at maximum buffer size")
|
||||
dec.stream.setPosition(dec.appendPosition)
|
||||
dec.stream.writeData(buf, len)
|
||||
inc(dec.appendPosition, len)
|
||||
assert dec.appendPosition == dec.stream.getPosition()
|
||||
|
||||
proc feed*[T: byte|char](dec: var BufferedDecoder; data: openarray[T]) =
|
||||
if data.len > 0:
|
||||
dec.feed(unsafeAddr data[0], data.len)
|
||||
|
||||
proc decode*(dec: var BufferedDecoder; E = void): (bool, Preserve[E]) =
|
||||
## Decode from `dec`. If decoding fails the internal position of the
|
||||
## decoder does not advance.
|
||||
if dec.appendPosition > 0:
|
||||
assert(dec.decodePosition < dec.appendPosition)
|
||||
dec.stream.setPosition(dec.decodePosition)
|
||||
try:
|
||||
result[1] = decodePreserves(dec.stream, E)
|
||||
result[0] = true
|
||||
dec.decodePosition = dec.stream.getPosition()
|
||||
if dec.decodePosition == dec.appendPosition:
|
||||
dec.stream.setPosition(0)
|
||||
dec.stream.data.setLen(0)
|
||||
dec.appendPosition = 0
|
||||
dec.decodePosition = 0
|
||||
except IOError:
|
||||
discard
|
||||
|
||||
template preservesRecord*(label: string) {.pragma.}
|
||||
## Serialize this object or tuple as a record.
|
||||
## See ``toPreserve``.
|
||||
|
@ -1413,131 +887,3 @@ proc getOrDefault*[T, V](pr: Preserve[T]; key: string; default: V): V =
|
|||
if fromPreserve(result, v): return
|
||||
else: break
|
||||
default
|
||||
|
||||
type TextMode* = enum textPreserves, textJson
|
||||
|
||||
proc writeText*[E](stream: Stream; pr: Preserve[E]; mode = textPreserves) =
|
||||
## Encode Preserves to a `Stream` as text.
|
||||
if pr.embedded: write(stream, "#!")
|
||||
case pr.kind:
|
||||
of pkBoolean:
|
||||
case pr.bool
|
||||
of false: write(stream, "#f")
|
||||
of true: write(stream, "#t")
|
||||
of pkFloat:
|
||||
write(stream, $pr.float)
|
||||
write(stream, 'f')
|
||||
of pkDouble:
|
||||
write(stream, $pr.double)
|
||||
of pkSignedInteger:
|
||||
write(stream, $pr.int)
|
||||
of pkString:
|
||||
write(stream, escapeJson(pr.string))
|
||||
of pkByteString:
|
||||
if pr.bytes.allIt(char(it) in {' '..'!', '#'..'~'}):
|
||||
write(stream, "#\"")
|
||||
write(stream, cast[string](pr.bytes))
|
||||
write(stream, '"')
|
||||
else:
|
||||
if pr.bytes.len > 64:
|
||||
write(stream, "#[") #]#
|
||||
write(stream, base64.encode(pr.bytes))
|
||||
write(stream, ']')
|
||||
else:
|
||||
const alphabet = "0123456789abcdef"
|
||||
write(stream, "#x\"")
|
||||
for b in pr.bytes:
|
||||
write(stream, alphabet[int(b shr 4)])
|
||||
write(stream, alphabet[int(b and 0xf)])
|
||||
write(stream, '"')
|
||||
of pkSymbol:
|
||||
let sym = pr.symbol.string
|
||||
if sym.len > 0 and sym[0] in {'A'..'z'} and not sym.anyIt(char(it) in { '\x00'..'\x19', '"', '\\', '|' }):
|
||||
write(stream, sym)
|
||||
else:
|
||||
write(stream, '|')
|
||||
for c in sym:
|
||||
case c
|
||||
of '\\':
|
||||
write(stream, "\\\\")
|
||||
of '/':
|
||||
write(stream, "\\/")
|
||||
of '\x08':
|
||||
write(stream, "\\b")
|
||||
of '\x0c':
|
||||
write(stream, "\\f")
|
||||
of '\x0a':
|
||||
write(stream, "\\n")
|
||||
of '\x0d':
|
||||
write(stream, "\\r")
|
||||
of '\x09':
|
||||
write(stream, "\\t")
|
||||
of '|':
|
||||
write(stream, "\\|")
|
||||
else:
|
||||
write(stream, c)
|
||||
write(stream, '|')
|
||||
of pkRecord:
|
||||
assert(pr.record.len > 0)
|
||||
write(stream, '<')
|
||||
writeText(stream, pr.record[pr.record.high], mode)
|
||||
for i in 0..<pr.record.high:
|
||||
write(stream, ' ')
|
||||
writeText(stream, pr.record[i], mode)
|
||||
write(stream, '>')
|
||||
of pkSequence:
|
||||
write(stream, '[')
|
||||
var insertSeperator: bool
|
||||
case mode
|
||||
of textPreserves:
|
||||
for val in pr.sequence:
|
||||
if insertSeperator: write(stream, ' ')
|
||||
else: insertSeperator = true
|
||||
writeText(stream, val, mode)
|
||||
of textJson:
|
||||
for val in pr.sequence:
|
||||
if insertSeperator: write(stream, ',')
|
||||
else: insertSeperator = true
|
||||
writeText(stream, val, mode)
|
||||
write(stream, ']')
|
||||
of pkSet:
|
||||
write(stream, "#{")
|
||||
var insertSeperator: bool
|
||||
for val in pr.set.items:
|
||||
if insertSeperator: write(stream, ' ')
|
||||
else: insertSeperator = true
|
||||
writeText(stream, val, mode)
|
||||
write(stream, '}')
|
||||
of pkDictionary:
|
||||
write(stream, '{')
|
||||
var insertSeperator: bool
|
||||
case mode
|
||||
of textPreserves:
|
||||
for (key, value) in pr.dict.items:
|
||||
if insertSeperator: write(stream, ' ')
|
||||
else: insertSeperator = true
|
||||
writeText(stream, key, mode)
|
||||
write(stream, ": ")
|
||||
writeText(stream, value, mode)
|
||||
of textJson:
|
||||
for (key, value) in pr.dict.items:
|
||||
if insertSeperator: write(stream, ',')
|
||||
else: insertSeperator = true
|
||||
writeText(stream, key, mode)
|
||||
write(stream, ':')
|
||||
writeText(stream, value, mode)
|
||||
write(stream, '}')
|
||||
of pkEmbedded:
|
||||
write(stream, "#!")
|
||||
when compiles($pr.embed) and not E is void:
|
||||
write(stream, $pr.embed)
|
||||
else:
|
||||
write(stream, "…")
|
||||
|
||||
proc `$`*[E](pr: Preserve[E]): string =
|
||||
## Generate the textual representation of ``pr``.
|
||||
var stream = newStringStream()
|
||||
writeText(stream, pr, textPreserves)
|
||||
result = move stream.data
|
||||
|
||||
include ./preserves/private/parse
|
||||
|
|
|
@ -0,0 +1,166 @@
|
|||
# SPDX-FileCopyrightText: ☭ Emery Hemingway
|
||||
# SPDX-License-Identifier: Unlicense
|
||||
|
||||
import std/[endians, streams, strutils]
|
||||
import ./values
|
||||
|
||||
proc readVarint(s: Stream): uint =
|
||||
var
|
||||
shift = 0
|
||||
c = uint s.readUint8
|
||||
while (c and 0x80) == 0x80:
|
||||
result = result or ((c and 0x7f) shl shift)
|
||||
inc(shift, 7)
|
||||
c = uint s.readUint8
|
||||
result = result or (c shl shift)
|
||||
|
||||
proc decodePreserves*(s: Stream; E = void): Preserve[E] =
|
||||
## Decode a Preserves value from a binary-encoded stream.
|
||||
if s.atEnd: raise newException(IOError, "End of Preserves stream")
|
||||
const endMarker = 0x84
|
||||
let tag = s.readUint8()
|
||||
case tag
|
||||
of 0x80: result = Preserve[E](kind: pkBoolean, bool: false)
|
||||
of 0x81: result = Preserve[E](kind: pkBoolean, bool: true)
|
||||
of 0x85:
|
||||
discard decodePreserves(s, E)
|
||||
result = decodePreserves(s, E)
|
||||
of 0x86:
|
||||
result = decodePreserves(s, E)
|
||||
result.embedded = true
|
||||
of 0x87:
|
||||
let n = s.readUint8()
|
||||
case n
|
||||
of 4:
|
||||
when system.cpuEndian == bigEndian:
|
||||
result = Preserve[E](kind: pkFloat, float: s.readFloat32())
|
||||
else:
|
||||
result = Preserve[E](kind: pkFloat)
|
||||
var be = s.readFloat32()
|
||||
swapEndian32(result.float.addr, be.addr)
|
||||
of 8:
|
||||
when system.cpuEndian == bigEndian:
|
||||
result = Preserve[E](kind: pkDouble, double: s.readFloat64())
|
||||
else:
|
||||
result = Preserve[E](kind: pkDouble)
|
||||
var be = s.readFloat64()
|
||||
swapEndian64(result.double.addr, be.addr)
|
||||
else:
|
||||
raise newException(IOError, "unhandled IEEE754 value of " & $n & " bytes")
|
||||
of 0xb1:
|
||||
var data = newString(s.readVarint())
|
||||
if data.len > 0:
|
||||
let n = s.readData(unsafeAddr data[0], data.len)
|
||||
if n != data.len:
|
||||
raise newException(IOError, "short read")
|
||||
result = Preserve[E](kind: pkString, string: data)
|
||||
of 0xb2:
|
||||
var data = newSeq[byte](s.readVarint())
|
||||
if data.len > 0:
|
||||
let n = s.readData(addr data[0], data.len)
|
||||
if n != data.len:
|
||||
raise newException(IOError, "short read")
|
||||
result = Preserve[E](kind: pkByteString, bytes: data)
|
||||
of 0xb3:
|
||||
var data = newString(s.readVarint())
|
||||
if data.len > 0:
|
||||
let n = s.readData(addr data[0], data.len)
|
||||
if n != data.len:
|
||||
raise newException(IOError, "short read")
|
||||
result = Preserve[E](kind: pkSymbol, symbol: Symbol data)
|
||||
of 0xb4:
|
||||
result = Preserve[E](kind: pkRecord)
|
||||
var label = decodePreserves(s, E)
|
||||
while s.peekUint8() != endMarker:
|
||||
result.record.add decodePreserves(s, E)
|
||||
result.record.add(move label)
|
||||
discard s.readUint8()
|
||||
of 0xb5:
|
||||
result = Preserve[E](kind: pkSequence)
|
||||
while s.peekUint8() != endMarker:
|
||||
result.sequence.add decodePreserves(s, E)
|
||||
discard s.readUint8()
|
||||
of 0xb6:
|
||||
result = Preserve[E](kind: pkSet)
|
||||
while s.peekUint8() != endMarker:
|
||||
incl(result, decodePreserves(s, E))
|
||||
discard s.readUint8()
|
||||
of 0xb7:
|
||||
result = Preserve[E](kind: pkDictionary)
|
||||
while s.peekUint8() != endMarker:
|
||||
result[decodePreserves(s, E)] = decodePreserves(s, E)
|
||||
discard s.readUint8()
|
||||
of 0xb0:
|
||||
var len = s.readVarint()
|
||||
result = Preserve[E](kind: pkSignedInteger)
|
||||
if len > 0:
|
||||
if (s.peekUint8() and 0x80) == 0x80:
|
||||
result.int = BiggestInt -1
|
||||
while len > 0:
|
||||
result.int = (result.int shl 8) + s.readUint8().BiggestInt
|
||||
dec(len)
|
||||
|
||||
of endMarker:
|
||||
raise newException(ValueError, "invalid Preserves stream")
|
||||
else:
|
||||
raise newException(ValueError, "invalid Preserves tag byte 0x" & tag.toHex(2))
|
||||
|
||||
proc decodePreserves*(s: string; E = void): Preserve[E] =
|
||||
## Decode a string of binary-encoded Preserves.
|
||||
decodePreserves(s.newStringStream, E)
|
||||
|
||||
proc decodePreserves*(s: seq[byte]; E = void): Preserve[E] =
|
||||
## Decode a byte-string of binary-encoded Preserves.
|
||||
decodePreserves(cast[string](s), E)
|
||||
|
||||
type BufferedDecoder* = object
|
||||
## Type for buffering binary Preserves before decoding.
|
||||
stream: StringStream
|
||||
appendPosition, decodePosition, maxSize: int
|
||||
|
||||
proc newBufferedDecoder*(maxSize = 4096): BufferedDecoder =
|
||||
## Create a new `newBufferedDecoder`.
|
||||
runnableExamples:
|
||||
var
|
||||
buf = newBufferedDecoder()
|
||||
bin = encode(parsePreserves("<foobar>"))
|
||||
buf.feed(bin[0..2])
|
||||
buf.feed(bin[3..bin.high])
|
||||
var (success, pr) = decode(buf)
|
||||
assert success
|
||||
assert $pr == "<foobar>"
|
||||
BufferedDecoder(
|
||||
stream: newStringStream(newStringOfCap(maxSize)),
|
||||
maxSize: maxSize,
|
||||
)
|
||||
|
||||
proc feed*(dec: var BufferedDecoder; buf: pointer; len: int) =
|
||||
assert len > 0
|
||||
if dec.maxSize > 0 and dec.maxSize < (dec.appendPosition + len):
|
||||
raise newException(IOError, "BufferedDecoder at maximum buffer size")
|
||||
dec.stream.setPosition(dec.appendPosition)
|
||||
dec.stream.writeData(buf, len)
|
||||
inc(dec.appendPosition, len)
|
||||
assert dec.appendPosition == dec.stream.getPosition()
|
||||
|
||||
proc feed*[T: byte|char](dec: var BufferedDecoder; data: openarray[T]) =
|
||||
if data.len > 0:
|
||||
dec.feed(unsafeAddr data[0], data.len)
|
||||
|
||||
proc decode*(dec: var BufferedDecoder; E = void): (bool, Preserve[E]) =
|
||||
## Decode from `dec`. If decoding fails the internal position of the
|
||||
## decoder does not advance.
|
||||
if dec.appendPosition > 0:
|
||||
assert(dec.decodePosition < dec.appendPosition)
|
||||
dec.stream.setPosition(dec.decodePosition)
|
||||
try:
|
||||
result[1] = decodePreserves(dec.stream, E)
|
||||
result[0] = true
|
||||
dec.decodePosition = dec.stream.getPosition()
|
||||
if dec.decodePosition == dec.appendPosition:
|
||||
dec.stream.setPosition(0)
|
||||
dec.stream.data.setLen(0)
|
||||
dec.appendPosition = 0
|
||||
dec.decodePosition = 0
|
||||
except IOError:
|
||||
discard
|
|
@ -0,0 +1,100 @@
|
|||
# SPDX-FileCopyrightText: ☭ Emery Hemingway
|
||||
# SPDX-License-Identifier: Unlicense
|
||||
|
||||
import std/[endians, options, sets, sequtils, streams, tables, typetraits]
|
||||
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 pkSignedInteger:
|
||||
if pr.int == 0:
|
||||
str.write("\xb0\x00")
|
||||
else:
|
||||
var bitCount = 1'u8
|
||||
if pr.int < 0:
|
||||
while ((not pr.int) shr bitCount) != 0:
|
||||
inc(bitCount)
|
||||
else:
|
||||
while (pr.int shr bitCount) != 0:
|
||||
inc(bitCount)
|
||||
var byteCount = (bitCount + 8) div 8
|
||||
str.write(0xb0'u8)
|
||||
str.writeVarint(byteCount)
|
||||
proc write(n: uint8; i: BiggestInt) =
|
||||
if n > 1:
|
||||
write(n.pred, i shr 8)
|
||||
str.write(i.uint8)
|
||||
write(byteCount, pr.int)
|
||||
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)
|
|
@ -1,16 +1,12 @@
|
|||
# SPDX-FileCopyrightText: 2021 ☭ Emery Hemingway
|
||||
# SPDX-FileCopyrightText: ☭ Emery Hemingway
|
||||
# SPDX-License-Identifier: Unlicense
|
||||
|
||||
# this module is included in ../../preserves.nim
|
||||
|
||||
import std/[parseutils, unicode]
|
||||
|
||||
when isMainModule:
|
||||
import std/strutils
|
||||
from std/sequtils import insert
|
||||
import std/[base64, parseutils, strutils, unicode]
|
||||
from std/sequtils import insert
|
||||
|
||||
import npeg
|
||||
import ../pegs
|
||||
import ./decoding, ./values
|
||||
|
||||
type
|
||||
Value = Preserve[void]
|
|
@ -0,0 +1,160 @@
|
|||
# SPDX-FileCopyrightText: ☭ Emery Hemingway
|
||||
# SPDX-License-Identifier: Unlicense
|
||||
|
||||
import std/[base64, json, options, sets, sequtils, streams, strutils, tables, typetraits]
|
||||
import ./values
|
||||
|
||||
proc `$`*(s: Symbol): string =
|
||||
let sym = string s
|
||||
if sym.len > 0 and sym[0] in {'A'..'z'} and not sym.anyIt(char(it) in { '\x00'..'\x19', '"', '\\', '|' }):
|
||||
result = sym
|
||||
else:
|
||||
result = newStringOfCap(sym.len shl 1)
|
||||
result.add('|')
|
||||
for c in sym:
|
||||
case c
|
||||
of '\\':
|
||||
result.add("\\\\")
|
||||
of '/':
|
||||
result.add("\\/")
|
||||
of '\x08':
|
||||
result.add("\\b")
|
||||
of '\x0c':
|
||||
result.add("\\f")
|
||||
of '\x0a':
|
||||
result.add("\\n")
|
||||
of '\x0d':
|
||||
result.add("\\r")
|
||||
of '\x09':
|
||||
result.add("\\t")
|
||||
of '|':
|
||||
result.add("\\|")
|
||||
else:
|
||||
result.add(c)
|
||||
result.add('|')
|
||||
|
||||
type TextMode* = enum textPreserves, textJson
|
||||
|
||||
proc writeText*[E](stream: Stream; pr: Preserve[E]; mode = textPreserves) =
|
||||
## Encode Preserves to a `Stream` as text.
|
||||
if pr.embedded: write(stream, "#!")
|
||||
case pr.kind:
|
||||
of pkBoolean:
|
||||
case pr.bool
|
||||
of false: write(stream, "#f")
|
||||
of true: write(stream, "#t")
|
||||
of pkFloat:
|
||||
write(stream, $pr.float)
|
||||
write(stream, 'f')
|
||||
of pkDouble:
|
||||
write(stream, $pr.double)
|
||||
of pkSignedInteger:
|
||||
write(stream, $pr.int)
|
||||
of pkString:
|
||||
write(stream, escapeJson(pr.string))
|
||||
of pkByteString:
|
||||
if pr.bytes.allIt(char(it) in {' '..'!', '#'..'~'}):
|
||||
write(stream, "#\"")
|
||||
write(stream, cast[string](pr.bytes))
|
||||
write(stream, '"')
|
||||
else:
|
||||
if pr.bytes.len > 64:
|
||||
write(stream, "#[") #]#
|
||||
write(stream, base64.encode(pr.bytes))
|
||||
write(stream, ']')
|
||||
else:
|
||||
const alphabet = "0123456789abcdef"
|
||||
write(stream, "#x\"")
|
||||
for b in pr.bytes:
|
||||
write(stream, alphabet[int(b shr 4)])
|
||||
write(stream, alphabet[int(b and 0xf)])
|
||||
write(stream, '"')
|
||||
of pkSymbol:
|
||||
let sym = pr.symbol.string
|
||||
if sym.len > 0 and sym[0] in {'A'..'z'} and not sym.anyIt(char(it) in { '\x00'..'\x19', '"', '\\', '|' }):
|
||||
write(stream, sym)
|
||||
else:
|
||||
write(stream, '|')
|
||||
for c in sym:
|
||||
case c
|
||||
of '\\':
|
||||
write(stream, "\\\\")
|
||||
of '/':
|
||||
write(stream, "\\/")
|
||||
of '\x08':
|
||||
write(stream, "\\b")
|
||||
of '\x0c':
|
||||
write(stream, "\\f")
|
||||
of '\x0a':
|
||||
write(stream, "\\n")
|
||||
of '\x0d':
|
||||
write(stream, "\\r")
|
||||
of '\x09':
|
||||
write(stream, "\\t")
|
||||
of '|':
|
||||
write(stream, "\\|")
|
||||
else:
|
||||
write(stream, c)
|
||||
write(stream, '|')
|
||||
of pkRecord:
|
||||
assert(pr.record.len > 0)
|
||||
write(stream, '<')
|
||||
writeText(stream, pr.record[pr.record.high], mode)
|
||||
for i in 0..<pr.record.high:
|
||||
write(stream, ' ')
|
||||
writeText(stream, pr.record[i], mode)
|
||||
write(stream, '>')
|
||||
of pkSequence:
|
||||
write(stream, '[')
|
||||
var insertSeperator: bool
|
||||
case mode
|
||||
of textPreserves:
|
||||
for val in pr.sequence:
|
||||
if insertSeperator: write(stream, ' ')
|
||||
else: insertSeperator = true
|
||||
writeText(stream, val, mode)
|
||||
of textJson:
|
||||
for val in pr.sequence:
|
||||
if insertSeperator: write(stream, ',')
|
||||
else: insertSeperator = true
|
||||
writeText(stream, val, mode)
|
||||
write(stream, ']')
|
||||
of pkSet:
|
||||
write(stream, "#{")
|
||||
var insertSeperator: bool
|
||||
for val in pr.set.items:
|
||||
if insertSeperator: write(stream, ' ')
|
||||
else: insertSeperator = true
|
||||
writeText(stream, val, mode)
|
||||
write(stream, '}')
|
||||
of pkDictionary:
|
||||
write(stream, '{')
|
||||
var insertSeperator: bool
|
||||
case mode
|
||||
of textPreserves:
|
||||
for (key, value) in pr.dict.items:
|
||||
if insertSeperator: write(stream, ' ')
|
||||
else: insertSeperator = true
|
||||
writeText(stream, key, mode)
|
||||
write(stream, ": ")
|
||||
writeText(stream, value, mode)
|
||||
of textJson:
|
||||
for (key, value) in pr.dict.items:
|
||||
if insertSeperator: write(stream, ',')
|
||||
else: insertSeperator = true
|
||||
writeText(stream, key, mode)
|
||||
write(stream, ':')
|
||||
writeText(stream, value, mode)
|
||||
write(stream, '}')
|
||||
of pkEmbedded:
|
||||
write(stream, "#!")
|
||||
when compiles($pr.embed) and not E is void:
|
||||
write(stream, $pr.embed)
|
||||
else:
|
||||
write(stream, "…")
|
||||
|
||||
proc `$`*[E](pr: Preserve[E]): string =
|
||||
## Generate the textual representation of ``pr``.
|
||||
var stream = newStringStream()
|
||||
writeText(stream, pr, textPreserves)
|
||||
result = move stream.data
|
|
@ -0,0 +1,247 @@
|
|||
# SPDX-FileCopyrightText: ☭ Emery Hemingway
|
||||
# SPDX-License-Identifier: Unlicense
|
||||
|
||||
import std/[hashes, options, sets, sequtils, tables]
|
||||
|
||||
type
|
||||
PreserveKind* = enum
|
||||
pkBoolean, pkFloat, pkDouble, pkSignedInteger, pkString, pkByteString, pkSymbol,
|
||||
pkRecord, pkSequence, pkSet, pkDictionary, pkEmbedded
|
||||
|
||||
const
|
||||
atomKinds* = {pkBoolean, pkFloat, pkDouble, pkSignedInteger, pkString, pkByteString, pkSymbol}
|
||||
compoundKinds* = {pkRecord, pkSequence, pkSet, pkDictionary}
|
||||
|
||||
type Symbol* = distinct string
|
||||
proc `<`*(x, y: Symbol): bool {.borrow.}
|
||||
proc `==`*(x, y: Symbol): bool {.borrow.}
|
||||
proc hash*(s: Symbol): Hash {.borrow.}
|
||||
proc len*(s: Symbol): int {.borrow.}
|
||||
|
||||
type
|
||||
Preserve*[E] = object
|
||||
embedded*: bool
|
||||
## Flag to mark embedded Preserves
|
||||
case kind*: PreserveKind
|
||||
of pkBoolean:
|
||||
bool*: bool
|
||||
of pkFloat:
|
||||
float*: float32
|
||||
of pkDouble:
|
||||
double*: float64
|
||||
of pkSignedInteger:
|
||||
int*: BiggestInt
|
||||
of pkString:
|
||||
string*: string
|
||||
of pkByteString:
|
||||
bytes*: seq[byte]
|
||||
of pkSymbol:
|
||||
symbol*: Symbol
|
||||
of pkRecord:
|
||||
record*: seq[Preserve[E]] # label is last
|
||||
of pkSequence:
|
||||
sequence*: seq[Preserve[E]]
|
||||
of pkSet:
|
||||
set*: seq[Preserve[E]]
|
||||
# TODO: HashSet
|
||||
of pkDictionary:
|
||||
dict*: seq[DictEntry[E]]
|
||||
# TODO: Tables
|
||||
of pkEmbedded:
|
||||
embed*: E
|
||||
|
||||
DictEntry*[E] = tuple[key: Preserve[E], val: Preserve[E]]
|
||||
|
||||
func `==`*[A, B](x: Preserve[A]; y: Preserve[B]): bool =
|
||||
## Check `x` and `y` for equivalence.
|
||||
if x.kind == y.kind and x.embedded == y.embedded:
|
||||
case x.kind
|
||||
of pkBoolean:
|
||||
result = x.bool == y.bool
|
||||
of pkFloat:
|
||||
result = x.float == y.float
|
||||
of pkDouble:
|
||||
result = x.double == y.double
|
||||
of pkSignedInteger:
|
||||
result = x.int == y.int
|
||||
of pkString:
|
||||
result = x.string == y.string
|
||||
of pkByteString:
|
||||
result = x.bytes == y.bytes
|
||||
of pkSymbol:
|
||||
result = x.symbol == y.symbol
|
||||
of pkRecord:
|
||||
result = x.record.len == y.record.len
|
||||
for i in 0..x.record.high:
|
||||
if not result: break
|
||||
result = result and (x.record[i] == y.record[i])
|
||||
of pkSequence:
|
||||
for i, val in x.sequence:
|
||||
if y.sequence[i] != val: return false
|
||||
result = true
|
||||
of pkSet:
|
||||
result = x.set.len == y.set.len
|
||||
for i in 0..x.set.high:
|
||||
if not result: break
|
||||
result = result and (x.set[i] == y.set[i])
|
||||
of pkDictionary:
|
||||
result = x.dict.len == y.dict.len
|
||||
for i in 0..x.dict.high:
|
||||
if not result: break
|
||||
result = result and
|
||||
(x.dict[i].key == y.dict[i].key) and
|
||||
(x.dict[i].val == y.dict[i].val)
|
||||
of pkEmbedded:
|
||||
when A is B:
|
||||
when A is void:
|
||||
result = true
|
||||
else:
|
||||
result = x.embed == y.embed
|
||||
|
||||
proc `<`(x, y: string | seq[byte]): bool =
|
||||
for i in 0 .. min(x.high, y.high):
|
||||
if x[i] < y[i]: return true
|
||||
if x[i] != y[i]: return false
|
||||
x.len < y.len
|
||||
|
||||
proc `<`*[A, B](x: Preserve[A]; y: Preserve[B]): bool =
|
||||
## Preserves have a total order over values. Check if `x` is ordered before `y`.
|
||||
if x.embedded != y.embedded:
|
||||
result = y.embedded
|
||||
elif x.kind != y.kind:
|
||||
result = x.kind < y.kind
|
||||
else:
|
||||
case x.kind
|
||||
of pkBoolean:
|
||||
result = (not x.bool) and y.bool
|
||||
of pkFloat:
|
||||
result = x.float < y.float
|
||||
of pkDouble:
|
||||
result = x.double < y.double
|
||||
of pkSignedInteger:
|
||||
result = x.int < y.int
|
||||
of pkString:
|
||||
result = x.string < y.string
|
||||
of pkByteString:
|
||||
result = x.bytes < y.bytes
|
||||
of pkSymbol:
|
||||
result = x.symbol < y.symbol
|
||||
of pkRecord:
|
||||
if x.record[x.record.high] < y.record[y.record.high]: return true
|
||||
for i in 0..<min(x.record.high, y.record.high):
|
||||
if x.record[i] < y.record[i]: return true
|
||||
if x.record[i] == y.record[i]: return false
|
||||
result = x.record.len < y.record.len
|
||||
of pkSequence:
|
||||
for i in 0..min(x.sequence.high, y.sequence.high):
|
||||
if x.sequence[i] < y.sequence[i]: return true
|
||||
if x.sequence[i] != y.sequence[i]: return false
|
||||
result = x.sequence.len < y.sequence.len
|
||||
of pkSet:
|
||||
for i in 0..min(x.set.high, y.set.high):
|
||||
if x.set[i] < y.set[i]: return true
|
||||
if x.set[i] != y.set[i]: return false
|
||||
result = x.set.len < y.set.len
|
||||
of pkDictionary:
|
||||
for i in 0..min(x.dict.high, y.dict.high):
|
||||
if x.dict[i].key < y.dict[i].key: return true
|
||||
if x.dict[i].key == y.dict[i].key:
|
||||
if x.dict[i].val < y.dict[i].val: return true
|
||||
if x.dict[i].val != y.dict[i].val: return false
|
||||
result = x.dict.len < y.dict.len
|
||||
of pkEmbedded:
|
||||
when (not A is void) and (A is B):
|
||||
result = x.embed < y.embed
|
||||
|
||||
func cmp*[E](x, y: Preserve[E]): int =
|
||||
## Compare by Preserves total ordering.
|
||||
if x == y: 0
|
||||
elif x < y: -1
|
||||
else: 1
|
||||
|
||||
proc sort*[E](pr: var Preserve[E]) = sort(pr.sequence, cmp)
|
||||
## Sort a Preserves array by total ordering.
|
||||
|
||||
proc hash*[E](pr: Preserve[E]): Hash =
|
||||
## Produce a `Hash` of `pr` for use with a `HashSet` or `Table`.
|
||||
var h = hash(pr.kind.int) !& hash(pr.embedded)
|
||||
case pr.kind
|
||||
of pkBoolean:
|
||||
h = h !& hash(pr.bool)
|
||||
of pkFloat:
|
||||
h = h !& hash(pr.float)
|
||||
of pkDouble:
|
||||
h = h !& hash(pr.double)
|
||||
of pkSignedInteger:
|
||||
h = h !& hash(pr.int)
|
||||
of pkString:
|
||||
h = h !& hash(pr.string)
|
||||
of pkByteString:
|
||||
h = h !& hash(pr.bytes)
|
||||
of pkSymbol:
|
||||
h = h !& hash(string pr.symbol)
|
||||
of pkRecord:
|
||||
for val in pr.record:
|
||||
h = h !& hash(val)
|
||||
of pkSequence:
|
||||
for val in pr.sequence:
|
||||
h = h !& hash(val)
|
||||
of pkSet:
|
||||
for val in pr.set.items:
|
||||
h = h !& hash(val)
|
||||
of pkDictionary:
|
||||
for (key, val) in pr.dict.items:
|
||||
h = h !& hash(key) !& hash(val)
|
||||
of pkEmbedded:
|
||||
when E is void:
|
||||
h = h !& hash(pr.embed)
|
||||
else:
|
||||
if pr.embed.isNil:
|
||||
h = h !& hash(false)
|
||||
else:
|
||||
h = h !& hash(pr.embed)
|
||||
!$h
|
||||
|
||||
proc `[]`*(pr: Preserve; i: int): Preserve =
|
||||
## Select an indexed value from ``pr``.
|
||||
## Only valid for records and sequences.
|
||||
case pr.kind
|
||||
of pkRecord: pr.record[i]
|
||||
of pkSequence: pr.sequence[i]
|
||||
else:
|
||||
raise newException(ValueError, "Preserves value is not indexable")
|
||||
|
||||
proc `[]=`*(pr: var Preserve; i: Natural; val: Preserve) =
|
||||
## Assign an indexed value into ``pr``.
|
||||
## Only valid for records and sequences.
|
||||
case pr.kind
|
||||
of pkRecord: pr.record[i] = val
|
||||
of pkSequence: pr.sequence[i] = val
|
||||
else:
|
||||
raise newException(ValueError, "`Preserves value is not indexable")
|
||||
|
||||
proc `[]=`*(pr: var Preserve; key, val: Preserve) =
|
||||
## Insert `val` by `key` in the Preserves dictionary `pr`.
|
||||
for i in 0..pr.dict.high:
|
||||
if key < pr.dict[i].key:
|
||||
insert(pr.dict, [(key, val, )], i)
|
||||
return
|
||||
elif key == pr.dict[i].key:
|
||||
pr.dict[i].val = val
|
||||
return
|
||||
pr.dict.add((key, val, ))
|
||||
|
||||
proc incl*(pr: var Preserve; key: Preserve) =
|
||||
## Include `key` in the Preserves set `pr`.
|
||||
for i in 0..pr.set.high:
|
||||
if key < pr.set[i]:
|
||||
insert(pr.set, [key], i)
|
||||
return
|
||||
pr.set.add(key)
|
||||
|
||||
proc excl*(pr: var Preserve; key: Preserve) =
|
||||
## Exclude `key` from the Preserves set `pr`.
|
||||
for i in 0..pr.set.high:
|
||||
if pr.set[i] == key:
|
||||
delete(pr.set, i..i)
|
||||
break
|
Loading…
Reference in New Issue