Record utilities

This commit is contained in:
Emery Hemingway 2021-06-08 12:14:56 +02:00
parent b5940cfe22
commit b099475d25
1 changed files with 122 additions and 83 deletions

View File

@ -4,11 +4,13 @@ import base64, endians, json, hashes, tables, streams
import bigints
type
PreserveKind = enum
PreserveKind* = enum
pkBoolean, pkFloat, pkDouble, pkSignedInteger, pkBigInteger, pkString, pkByteString,
pkSymbol, pkRecord, pkSequence, pkSet, pkDictionary, pkEmbedded
Preserve*[T] = object
Preserve*[T] {.acyclic.} = object
## Type that stores a Preserves value.
## ``T`` is the domain-specific type of "embedded" values, otherwise ``void``.
case kind*: PreserveKind
of pkBoolean:
bool*: bool
@ -37,17 +39,111 @@ type
of pkEmbedded:
embedded*: T
proc `$`*[T](prs: Preserve[T]): string =
case prs.kind:
of pkBoolean:
case prs.bool
of false: result = "#f"
of true: result = "#t"
of pkFloat:
result = $prs.float & "f"
of pkDouble:
result = $prs.double
of pkSignedInteger:
result = $prs.int
of pkBigInteger:
result = $prs.bigint
of pkString:
result = escapeJson(prs.string)
of pkByteString:
result.add("#[")
result.add(base64.encode(prs.bytes))
result.add(']')
of pkSymbol:
result.add(escapeJsonUnquoted(prs.symbol))
of pkRecord:
result.add('<')
for val in prs.record:
result.add(' ')
result.add($val)
result.add('>')
of pkSequence:
result.add('[')
for i, val in prs.seq:
if i > 0:
result.add(' ')
result.add($val)
result.add(']')
of pkSet:
result.add("#{")
for val in prs.set.keys:
result.add($val)
result.add(' ')
if result.len > 1:
result.setLen(result.high)
result.add('}')
of pkDictionary:
result.add('{')
for (key, value) in prs.dict.pairs:
result.add($key)
result.add(" :")
result.add($value)
result.add(' ')
if result.len > 1:
result.setLen(result.high)
result.add('}')
of pkEmbedded:
when not T is void:
$prs.embedded
proc toPreserve*(b: bool): Preserve[void] =
Preserve[void](kind: pkBoolean, bool: b)
proc toPreserve*(n: SomeInteger): Preserve[void] =
Preserve[void](kind: pkSignedInteger, int: n.BiggestInt)
proc toPreserve*(n: BigInt): Preserve[void] =
if initBigInt(low(BiggestInt)) < n and n < initBigInt(high(BiggestInt)):
var tmp: BiggestUint
for limb in n.limbs:
tmp = (tmp shl 32) or limb
if Negative in n.flags:
tmp = (not tmp) + 1
result = Preserve[void](kind: pkSignedInteger, int: cast[BiggestInt](tmp))
else:
result = Preserve[void](kind: pkBigInteger, bigint: n)
proc toPreserve*(s: string): Preserve[void] =
Preserve[void](kind: pkString, string: s)
proc symbol*[T](s: string): Preserve[void] {.inline.} =
## Symbol constructor.
Preserve[T](kind: pkSymbol, symbol: s)
proc record*[T](label: Preserve[T], args: varargs[Preserve[T]]): Preserve[T] =
## Record constructor.
result = Preserve[T](kind: pkRecord, record: newSeqOfCap(1+args.len))
result.record.add(label)
for arg in args: result.record.add(arg)
proc record*[T](label: string, args: varargs[Preserve[T]]): Preserve[T] {.inline.} =
## Record constructor that converts ``label`` to a symbol.
record(symbol[T](label), args)
proc label*[T](prs: Preserve[T]): Preserve[T] {.inline.} =
## Return the label of a record value.
prs.record[0]
proc arity*[T](prs: Preserve[T]): int {.inline.} =
## Return the number of fields in a record value.
pred(prs.record.len)
proc fields*[T](prs: Preserve[T]): seq[Preserve[T]] {.inline.} =
## Return the fields of a record value.
prs.record[1..prs.record.high]
iterator fields*[T](prs: Preserve[T]): seq[Preserve[T]] =
iterator fields*[T](prs: Preserve[T]): Preserve[T] =
## Iterate the fields of a record value.
for i in 1..prs.record.high:
yield prs.record[i]
@ -160,66 +256,6 @@ proc hash*[T](prs: Preserve[T]): Hash =
h = h !& hash(prs.embedded)
!$h
proc `$`*[T](prs: Preserve[T]): string =
case prs.kind:
of pkBoolean:
case prs.bool
of false: result = "#f"
of true: result = "#t"
of pkFloat:
result = $prs.float & "f"
of pkDouble:
result = $prs.double
of pkSignedInteger:
result = $prs.int
of pkBigInteger:
result = $prs.bigint
of pkString:
result = escapeJson(prs.string)
of pkByteString:
result.add("#[")
result.add(base64.encode(prs.bytes))
result.add(']')
of pkSymbol:
result.add('|')
result.add(escapeJsonUnquoted(prs.symbol))
result.add('|')
of pkRecord:
result.add('<')
result.add(prs.label)
for val in prs.fields:
result.add(' ')
result.add($val)
result.add('>')
of pkSequence:
result.add('[')
for i, val in prs.seq:
if i > 0:
result.add(' ')
result.add($val)
result.add(']')
of pkSet:
result.add("#{")
for val in prs.set.keys:
result.add($val)
result.add(' ')
if result.len > 1:
result.setLen(result.high)
result.add('}')
of pkDictionary:
result.add('{')
for (key, value) in prs.dict.pairs:
result.add($key)
result.add(" :")
result.add($value)
result.add(' ')
if result.len > 1:
result.setLen(result.high)
result.add('}')
of pkEmbedded:
when not T is void:
$prs.embedded
proc writeVarint(s: Stream; n: int) =
var n = n
while true:
@ -420,26 +456,6 @@ proc parsePreserve*(s: Stream): Preserve[void] =
else:
assertStream(false)
proc toPreserve*(b: bool): Preserve[void] =
Preserve[void](kind: pkBoolean, bool: b)
proc toPreserve*(n: SomeInteger): Preserve[void] =
Preserve[void](kind: pkSignedInteger, int: n.BiggestInt)
proc toPreserve*(n: BigInt): Preserve[void] =
if initBigInt(low(BiggestInt)) < n and n < initBigInt(high(BiggestInt)):
var tmp: BiggestUint
for limb in n.limbs:
tmp = (tmp shl 32) or limb
if Negative in n.flags:
tmp = (not tmp) + 1
result = Preserve[void](kind: pkSignedInteger, int: cast[BiggestInt](tmp))
else:
result = Preserve[void](kind: pkBigInteger, bigint: n)
proc toPreserve*(s: string): Preserve[void] =
Preserve[void](kind: pkString, string: s)
proc toPreserve*(js: JsonNode): Preserve[void] =
case js.kind
of JString:
@ -506,3 +522,26 @@ proc toJson*[T](prs: Preserve[T]): JsonNode =
result[key.string] = val.toJson
of pkEmbedded:
raise newException(ValueError, "cannot convert embedded value to JSON")
type Record* = object
## Type of a preserves record type.
label*: string
arity*: Natural
proc init*[T](rec: Record, fields: varargs[Preserve[T]]): Preserve[T] =
## Initialize a new record value.
assert(fields.len == rec.arity)
record(rec.label, fields)
proc isClassOf*[T](rec: Record, val: Preserve[T]): bool =
## Compare the label and arity of ``val`` to the record type ``rec``.
if val.kind == pkRecord:
let label = val.label
if label.kind == pkSymbol:
result = label.symbol == rec.label and rec.arity == val.arity
proc classOf*[T](val: Preserve[T]): Record =
## Derive the ``Record`` type of ``val``.
if val.kind != pkRecord or val.label.kind == pkSymbol:
raise newException(ValueError, "cannot derive class of non-record value")
Record(label: val.label.symbol, arity: val.arity)