Store record labels behind fields

This commit is contained in:
Emery Hemingway 2021-06-24 17:31:30 +02:00
parent 6ebb752690
commit dddf82eedd
1 changed files with 141 additions and 73 deletions

View File

@ -5,8 +5,8 @@ import bigints
type
PreserveKind* = enum
pkBoolean, pkFloat, pkDouble, pkSignedInteger, pkBigInteger, pkString, pkByteString,
pkSymbol, pkRecord, pkSequence, pkSet, pkDictionary, pkEmbedded
pkBoolean, pkFloat, pkDouble, pkSignedInteger, pkBigInteger, pkString,
pkByteString, pkSymbol, pkRecord, pkSequence, pkSet, pkDictionary, pkEmbedded
Preserve* {.acyclic.} = object
## Type that stores a Preserves value.
@ -28,7 +28,7 @@ type
of pkSymbol:
symbol*: string
of pkRecord:
record*: seq[Preserve] # record[0] is the label
record*: seq[Preserve] # label is last
of pkSequence:
sequence*: seq[Preserve]
of pkSet:
@ -38,6 +38,26 @@ type
of pkEmbedded:
embedded*: pointer
proc assertValid*(prs: Preserve) =
case prs.kind
of pkBigInteger:
assert(BiggestInt.low.initBigInt < prs.bigint and prs.bigint < BiggestInt.high.initBigInt)
of pkRecord:
assert(prs.record.len > 0, "invalid Preserves record " & prs.repr)
assert(prs.record[prs.record.high].kind < pkRecord)
for v in prs.record: assertValid(v)
of pkSequence:
for v in prs.sequence: assertValid(v)
of pkSet:
for v in prs.set: assertValid(v)
of pkDictionary:
for key, val in prs.dict.pairs:
assert(key.kind < pkRecord)
assertValid(key)
assertValid(val)
else:
discard
proc `<`(x, y: string | seq[byte]): bool =
for i in 0 .. min(x.high, y.high):
if x[i] < y[i]:
@ -168,10 +188,12 @@ proc `$`*(prs: Preserve): string =
of pkSymbol:
result.add(escapeJsonUnquoted(prs.symbol))
of pkRecord:
assert(prs.record.len > 0)
result.add('<')
for val in prs.record:
result.add($prs.record[prs.record.high])
for i in 0..<prs.record.high:
result.add(' ')
result.add($val)
result.add($prs.record[i])
result.add('>')
of pkSequence:
result.add('[')
@ -201,6 +223,11 @@ proc `$`*(prs: Preserve): string =
of pkEmbedded:
result.add(prs.embedded.repr)
func isRecord*(prs: Preserve): bool =
if prs.kind == pkRecord:
result = true
assert(prs.record.len > 0)
proc isNamedTuple(T: typedesc): bool {.magic: "TypeTrait".}
proc distinctBase(T: typedesc): typedesc {.magic: "TypeTrait".}
template distinctBase[T](a: T): untyped = distinctBase(type(a))(a)
@ -209,20 +236,22 @@ proc symbol*(s: string): Preserve {.inline.} =
## Symbol constructor.
Preserve(kind: pkSymbol, symbol: s)
proc initRecord*(label: Preserve, args: varargs[Preserve]): Preserve =
proc initRecord*(label: Preserve; args: varargs[Preserve]): Preserve =
## Record constructor.
result = Preserve(kind: pkRecord,
record: newSeqOfCap[Preserve](1+args.len))
for arg in args:
assertValid(arg)
result.record.add(arg)
result.record.add(label)
for arg in args: result.record.add(arg)
proc initRecord*(label: string, args: varargs[Preserve]): Preserve {.inline.} =
proc initRecord*(label: string; args: varargs[Preserve]): Preserve {.inline.} =
## Record constructor that converts ``label`` to a symbol.
initRecord(symbol(label), args)
proc label*(prs: Preserve): Preserve {.inline.} =
## Return the label of a record value.
prs.record[0]
prs.record[prs.record.high]
proc arity*(prs: Preserve): int {.inline.} =
## Return the number of fields in a record value.
@ -230,11 +259,11 @@ proc arity*(prs: Preserve): int {.inline.} =
proc fields*(prs: Preserve): seq[Preserve] {.inline.} =
## Return the fields of a record value.
prs.record[1..prs.record.high]
prs.record[0..prs.record.high.pred]
iterator fields*(prs: Preserve): Preserve =
## Iterate the fields of a record value.
for i in 1..prs.record.high: yield prs.record[i]
for i in 0..<prs.record.high: yield prs.record[i]
proc writeVarint(s: Stream; n: int) =
var n = n
@ -326,9 +355,11 @@ proc write*(str: Stream; prs: Preserve) =
str.writeVarint(prs.symbol.len)
str.write(prs.symbol)
of pkRecord:
assert(prs.record.len > 0)
str.write(0xb4'u8)
for val in prs.record:
str.write(val)
str.write(prs.record[prs.record.high])
for i in 0..<prs.record.high:
str.write(prs.record[i])
str.write(0x84'u8)
of pkSequence:
str.write(0xb5'u8)
@ -388,10 +419,11 @@ proc parsePreserve*(s: Stream): Preserve =
result = symbol(s.readStr(len))
of 0xb4:
result = Preserve(kind: pkRecord)
var label = s.parsePreserve()
while s.peekUint8() != endMarker:
result.record.add(s.parsePreserve())
result.record.add(label)
discard s.readUint8()
assertStream(result.record.len > 0)
of 0xb5:
result = Preserve(kind: pkSequence)
while s.peekUint8() != endMarker:
@ -434,16 +466,42 @@ proc parsePreserve*(s: Stream): Preserve =
else:
assertStream(false)
proc toPreserveHook*(n: BigInt): Preserve =
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(kind: pkSignedInteger, int: cast[BiggestInt](tmp))
proc toPreserve*[T](x: T): Preserve =
## Serializes `x` to Preserves; uses `toPreserveHook(x: A)` if it's in scope to
## customize serialization.
when T is Preserve: result = x
elif T is Bigint:
result = Preserve(kind: pkBigInteger, bigint: x)
elif compiles(toPreserveHook(x)):
result = toPreserveHook(x)
elif T is array | seq:
result = Preserve(kind: pkSequence)
for v in x.items: result.sequence.add(toPreserve(v))
elif T is bool:
result = Preserve(kind: pkBoolean, bool: x)
elif T is distinct:
result = toPreserve(x.distinctBase)
elif T is float:
result = Preserve(kind: pkFloat, float: x)
elif T is float64:
result = Preserve(kind: pkDouble, double: x)
elif T is object | tuple:
result = Preserve(kind: pkDictionary)
for k, v in x.fieldPairs: result.dict[symbol(k)] = toPreserve(v)
elif T is Ordinal:
result = Preserve(kind: pkSignedInteger, int: x.ord.BiggestInt)
elif T is ptr | ref:
if system.`==`(x, nil): result = symbol"null"
else: result = toPreserve(x[])
elif T is string:
result = Preserve(kind: pkString, string: x)
elif T is SomeInteger:
result = Preserve(kind: pkSignedInteger, int: x.BiggestInt)
else:
result = Preserve(kind: pkBigInteger, bigint: n)
raiseAssert("cannot convert to Preserves: " & $T)
proc toPreserveHook*[T](set: HashSet[T]): Preserve =
Preserve(kind: pkSet, set: set.map(toPreserve))
proc toPreserveHook*(js: JsonNode): Preserve =
case js.kind
@ -469,41 +527,6 @@ proc toPreserveHook*(js: JsonNode): Preserve =
for i, e in js.elems:
result.sequence[i] = toPreserveHook(e)
proc toPreserve*[T](x: T): Preserve =
## Serializes `x` to Preserves; uses `toPreserveHook(x: A)` if it's in scope to
## customize serialization.
when T is Preserve: result = x
elif compiles(toPreserveHook(x)):
result = toPreserveHook(x)
elif T is array | seq:
result = Preserve(kind: pkSequence)
for v in x.items: result.sequence.add(toPreserve(v))
elif T is bool:
result = Preserve(kind: pkBoolean, bool: x)
elif T is distinct:
result = toPreserve(x.distinctBase)
elif T is float:
result = Preserve(kind: pkFloat, float: x)
elif T is float64:
result = Preserve(kind: pkDouble, double: x)
elif T is object:
result = Preserve(kind: pkDictionary)
for k, v in x.fieldPairs: result.dict[symbol(k)] = toPreserve(v)
elif T is Ordinal:
result = Preserve(kind: pkSignedInteger, int: x.ord.BiggestInt)
elif T is ptr | ref:
if system.`==`(x, nil): result = symbol"null"
else: result = toPreserve(x[])
elif T is string:
result = Preserve(kind: pkString, string: x)
elif T is SomeInteger:
result = Preserve(kind: pkSignedInteger, int: x.BiggestInt)
elif T is tuple:
result = Preserve(kind: pkRecord, record: @[symbol("")])
for v in x.fields: result.record.add(toPreserve(v))
else:
raiseAssert("cannot convert to Preserves: " & $T)
proc toJsonHook*(prs: Preserve): JsonNode =
case prs.kind:
of pkBoolean:
@ -547,28 +570,73 @@ proc toJsonHook*(prs: Preserve): JsonNode =
of pkEmbedded:
raise newException(ValueError, "cannot convert embedded value to JSON")
type Record* = object
## Type of a preserves record type.
label*: string
proc fromPreserve*[T](result: var T; prs: Preserve) =
# Inplace version of `preserveTo`.
when compiles(fromPreserveHook(result, prs)):
fromPreserveHook(result, prs)
elif T is Preserve:
result = prs
elif T is Bigint:
result = prs.bigint
elif T is bool:
result = prs.bool
elif T is SomeInteger:
result = T(prs.int)
elif T is float:
result = prs.float
elif T is seq:
result.setLen(prs.sequence.len)
for i, val in prs.sequence:
fromPreserve(result[i], val)
elif T is float64:
result = prs.double
elif T is object:
result = Preserve(kind: pkDictionary)
for k, v in x.fieldPairs: result.dict[symbol(k)] = toPreserve(v)
elif T is Ordinal:
result = Preserve(kind: pkSignedInteger, int: x.ord.BiggestInt)
elif T is ptr | ref:
if system.`==`(x, nil): result = symbol"null"
else: result = toPreserve(x[])
elif T is string:
result = Preserve(kind: pkString, string: x)
elif T is SomeInteger:
result = Preserve(kind: pkSignedInteger, int: x.BiggestInt)
elif T is tuple:
for i in 0..<prs.record.high:
fromPreserve(result[i], prs.record[i])
else:
raiseAssert("cannot convert from Preserves: " & $T)
proc preserveTo*(prs: Preserve; T: typedesc): T =
## Reverse of `toPreserve`.
fromPreserve(result, prs)
type RecordClass* = object
## Type of a preserves record.
label*: Preserve
arity*: Natural
proc init*(rec: Record, fields: varargs[Preserve]): Preserve =
proc `$`*(rec: RecordClass): string =
$rec.label & "/" & $rec.arity
proc init*(rec: RecordClass; fields: varargs[Preserve]): Preserve =
## Initialize a new record value.
assert(fields.len == rec.arity)
initRecord(rec.label, fields)
result = initRecord(rec.label, fields)
proc isClassOf*(rec: Record, val: Preserve): bool =
proc isClassOf*(rec: RecordClass; val: Preserve): 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
assert(val.record.len > 0)
result = val.label == rec.label and rec.arity == val.arity
proc classOf*(val: Preserve): 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)
proc classOf*(val: Preserve): RecordClass =
## Derive the ``RecordClass`` of ``val``.
if val.kind != pkRecord:
raise newException(ValueError, "cannot derive class of non-record value " & $val)
assert(val.record.len > 0)
RecordClass(label: val.label, arity: val.arity)
proc len*(prs: Preserve): int =
## Return the number of values one level below ``prs``.
@ -581,7 +649,7 @@ proc len*(prs: Preserve): int =
proc `[]`*(prs: Preserve; i: int): Preserve =
case prs.kind
of pkRecord: prs.record[succ(i)]
of pkRecord: prs.record[i]
of pkSequence: prs.sequence[i]
else:
raise newException(ValueError, "`[]` is not valid for " & $prs.kind)