Compare commits

...

2 Commits

Author SHA1 Message Date
Emery Hemingway 2dd63903f0 Remove records module
Redundant with toPreserve and fromPreserve.
2021-09-25 13:57:58 +02:00
Emery Hemingway 42a9b26458 Embeddable Preserves
Make Preserve a generic type that can embed a native Nim type.
Generate generic implementations from schemas and discard
embeddedType.
2021-09-25 13:47:05 +02:00
7 changed files with 464 additions and 363 deletions

View File

@ -10,16 +10,14 @@ from std/macros import hasCustomPragma, getCustomPragmaVal
type type
PreserveKind* = enum PreserveKind* = enum
pkBoolean, pkFloat, pkDouble, pkSignedInteger, pkBigInteger, pkString, pkByteString, pkSymbol, pkBoolean, pkFloat, pkDouble, pkSignedInteger, pkBigInteger, pkString, pkByteString, pkSymbol,
pkRecord, pkSequence, pkSet, pkDictionary pkRecord, pkSequence, pkSet, pkDictionary, pkEmbedded
const const
atomKinds* = {pkBoolean, pkFloat, pkDouble, pkSignedInteger, pkBigInteger, pkString, pkByteString, pkSymbol} atomKinds* = {pkBoolean, pkFloat, pkDouble, pkSignedInteger, pkBigInteger, pkString, pkByteString, pkSymbol}
compoundKinds* = {pkRecord, pkSequence, pkSet, pkDictionary} compoundKinds* = {pkRecord, pkSequence, pkSet, pkDictionary}
type type
DictEntry = tuple[key: Preserve, val: Preserve] Preserve*[E = void] {.acyclic.} = object
Preserve* {.acyclic.} = object
case kind*: PreserveKind case kind*: PreserveKind
of pkBoolean: of pkBoolean:
bool*: bool bool*: bool
@ -38,18 +36,24 @@ type
of pkSymbol: of pkSymbol:
symbol*: string symbol*: string
of pkRecord: of pkRecord:
record*: seq[Preserve] # label is last record*: seq[Preserve[E]] # label is last
of pkSequence: of pkSequence:
sequence*: seq[Preserve] sequence*: seq[Preserve[E]]
of pkSet: of pkSet:
set*: seq[Preserve] set*: seq[Preserve[E]]
# TODO: HashSet # TODO: HashSet
of pkDictionary: of pkDictionary:
dict*: seq[DictEntry] dict*: seq[DictEntry[E]]
# TODO: Tables # TODO: Tables
of pkEmbedded:
embed*: E
embedded*: bool embedded*: bool
## Flag to mark embedded Preserves
proc `==`*(x, y: Preserve): bool = DictEntry[E] = tuple[key: Preserve[E], val: Preserve[E]]
proc `==`*[A, B](x: Preserve[A]; y: Preserve[B]): bool =
## Check `x` and `y` for equivalence. ## Check `x` and `y` for equivalence.
if x.kind == y.kind and x.embedded == y.embedded: if x.kind == y.kind and x.embedded == y.embedded:
case x.kind case x.kind
@ -79,6 +83,12 @@ proc `==`*(x, y: Preserve): bool =
result = x.set == y.set result = x.set == y.set
of pkDictionary: of pkDictionary:
result = x.dict == y.dict result = x.dict == y.dict
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 = proc `<`(x, y: string | seq[byte]): bool =
for i in 0 .. min(x.high, y.high): for i in 0 .. min(x.high, y.high):
@ -86,7 +96,7 @@ proc `<`(x, y: string | seq[byte]): bool =
if x[i] != y[i]: return false if x[i] != y[i]: return false
x.len < y.len x.len < y.len
proc `<`*(x, y: Preserve): bool = proc `<`*[A, B](x: Preserve[A]; y: Preserve[B]): bool =
## Preserves have a total order over Values. Check if `x` is ordered before `y`. ## Preserves have a total order over Values. Check if `x` is ordered before `y`.
if x.embedded != y.embedded: if x.embedded != y.embedded:
result = y.embedded result = y.embedded
@ -138,6 +148,9 @@ proc `<`*(x, y: Preserve): bool =
if x.dict[i].val < y.dict[i].val: return true if x.dict[i].val < y.dict[i].val: return true
if x.dict[i].val != y.dict[i].val: return false if x.dict[i].val != y.dict[i].val: return false
result = x.dict.len < y.dict.len result = x.dict.len < y.dict.len
of pkEmbedded:
when (not A is void) and (A is B):
result = x.embed < y.embed
proc hash*(pr: Preserve): Hash = proc hash*(pr: Preserve): Hash =
## Produce a `Hash` of `pr` for use with a `HashSet` or `Table`. ## Produce a `Hash` of `pr` for use with a `HashSet` or `Table`.
@ -172,6 +185,8 @@ proc hash*(pr: Preserve): Hash =
of pkDictionary: of pkDictionary:
for (key, val) in pr.dict.items: for (key, val) in pr.dict.items:
h = h !& hash(key) !& hash(val) h = h !& hash(key) !& hash(val)
of pkEmbedded:
h = h !& hash(pr.embed)
!$h !$h
proc `[]`*(pr: Preserve; i: int): Preserve = proc `[]`*(pr: Preserve; i: int): Preserve =
@ -224,39 +239,37 @@ proc `[]=`*(pr: var Preserve; key, val: Preserve) =
return return
pr.dict.add((key, val, )) pr.dict.add((key, val, ))
proc symbol*(s: string; E = void): Preserve {.inline.} = proc symbol*[E](s: string): Preserve[E] {.inline.} =
## Create a Preserves symbol value. ## Create a Preserves symbol value.
Preserve(kind: pkSymbol, symbol: s) Preserve[E](kind: pkSymbol, symbol: s)
proc initRecord*(label: Preserve; arity = 0): Preserve = proc initRecord*[E](label: Preserve[E]; arity: Natural = 0): Preserve[E] =
## Create a Preserves record value. ## Create a Preserves record value.
result = Preserve(kind: pkRecord, record: newSeq[Preserve](arity.succ)) result = Preserve[E](kind: pkRecord, record: newSeq[Preserve[E]](arity.succ))
result.record[arity] = label result.record[arity] = label
proc initRecord*(label: Preserve; args: varargs[Preserve]): Preserve = proc initRecord*[E](label: Preserve[E]; args: varargs[Preserve[E]]): Preserve[E] =
## Create a Preserves record value. ## Create a Preserves record value.
result = Preserve(kind: pkRecord, result = Preserve[E](kind: pkRecord,
record: newSeqOfCap[Preserve](1+args.len)) record: newSeqOfCap[Preserve[E]](1+args.len))
for arg in args: for arg in args:
result.record.add(arg) result.record.add(arg)
result.record.add(label) result.record.add(label)
proc initRecord*(label: string; args: varargs[Preserve, toPreserve]): Preserve = proc initSequence*[E](len: Natural = 0): Preserve[E] =
## Convert ``label`` to a symbol and create a new record.
runnableExamples:
assert($initRecord("foo", 1, 2.0) == "<foo 1 2.0f>")
initRecord(symbol(label), args)
proc initSequence*(len = 0): Preserve =
## Create a Preserves sequence value. ## Create a Preserves sequence value.
Preserve(kind: pkSequence, record: newSeq[Preserve](len)) Preserve[E](kind: pkSequence, sequence: newSeq[Preserve[E]](len))
proc initSet*(): Preserve = Preserve(kind: pkSet) proc initSet*[E](): Preserve[E] = Preserve[E](kind: pkSet)
## Create a Preserves set value. ## Create a Preserves set value.
proc initDictionary*(): Preserve = Preserve(kind: pkDictionary) proc initDictionary*[E](): Preserve[E] = Preserve[E](kind: pkDictionary)
## Create a Preserves dictionary value. ## Create a Preserves dictionary value.
proc embed*[E](e: E): Preserve[E] =
## Create a Preserves value that embeds ``e``.
Preserve[E](kind: pkEmbedded, embed: e)
proc len*(pr: Preserve): int = proc len*(pr: Preserve): int =
## Return the shallow count of values in ``pr``, that is the number of ## Return the shallow count of values in ``pr``, that is the number of
## fields in a record, items in a sequence, items in a set, or key-value pairs ## fields in a record, items in a sequence, items in a set, or key-value pairs
@ -327,8 +340,10 @@ proc isSet*(pr: Preserve): bool {.inline.} = pr.kind == pkSet
proc isDictionary*(pr: Preserve): bool {.inline.} = pr.kind == pkDictionary proc isDictionary*(pr: Preserve): bool {.inline.} = pr.kind == pkDictionary
## Check if ``pr`` is a Preserves dictionary. ## Check if ``pr`` is a Preserves dictionary.
func isEmbedded*(pr: Preserve): bool {.inline.} = pr.embedded func isEmbedded*[E](pr: Preserve[E]): bool {.inline.} =
## Check if ``pr`` is an embedded value. ## Check if ``pr`` is an embedded value.
when E is void: pr.embedded # embedded Preserves
else: pr.kind == pkEmbedded # embedded Nim
proc label*(pr: Preserve): Preserve {.inline.} = proc label*(pr: Preserve): Preserve {.inline.} =
## Return the label of record value. ## Return the label of record value.
@ -366,7 +381,7 @@ proc readVarint(s: Stream): int =
break break
shift.inc 7 shift.inc 7
proc write*(str: Stream; pr: Preserve) = proc write*[E](str: Stream; pr: Preserve[E]) =
## Write the binary-encoding of a Preserves value to a stream. ## Write the binary-encoding of a Preserves value to a stream.
if pr.embedded: str.write(0x86'u8) if pr.embedded: str.write(0x86'u8)
case pr.kind: case pr.kind:
@ -460,15 +475,19 @@ proc write*(str: Stream; pr: Preserve) =
str.write(key) str.write(key)
str.write(value) str.write(value)
str.write(0x84'u8) str.write(0x84'u8)
of pkEmbedded:
when not E is void:
str.write(0x86'u8)
str.write(pr.embed.toPreserve)
proc encode*(pr: Preserve): seq[byte] = proc encode*[E](pr: Preserve[E]): seq[byte] =
## Return the binary-encoding of a Preserves value. ## Return the binary-encoding of a Preserves value.
let s = newStringStream() let s = newStringStream()
s.write pr s.write pr
s.setPosition 0 s.setPosition 0
result = cast[seq[byte]](s.readAll) result = cast[seq[byte]](s.readAll)
proc decodePreserves*(s: Stream): Preserve = proc decodePreserves*(s: Stream; E = void): Preserve[E] =
## Decode a Preserves value from a binary-encoded stream. ## Decode a Preserves value from a binary-encoded stream.
proc assertStream(check: bool) = proc assertStream(check: bool) =
if not check: if not check:
@ -476,61 +495,61 @@ proc decodePreserves*(s: Stream): Preserve =
const endMarker = 0x84 const endMarker = 0x84
let tag = s.readUint8() let tag = s.readUint8()
case tag case tag
of 0x80: result = Preserve(kind: pkBoolean, bool: false) of 0x80: result = Preserve[E](kind: pkBoolean, bool: false)
of 0x81: result = Preserve(kind: pkBoolean, bool: true) of 0x81: result = Preserve[E](kind: pkBoolean, bool: true)
of 0x82: of 0x82:
when system.cpuEndian == bigEndian: when system.cpuEndian == bigEndian:
result = Preserve(kind: pkFloat, float: s.readFloat32()) result = Preserve[E](kind: pkFloat, float: s.readFloat32())
else: else:
result = Preserve(kind: pkFloat) result = Preserve[E](kind: pkFloat)
var be = s.readFloat32() var be = s.readFloat32()
swapEndian32(result.float.addr, be.addr) swapEndian32(result.float.addr, be.addr)
of 0x83: of 0x83:
when system.cpuEndian == bigEndian: when system.cpuEndian == bigEndian:
result = Preserve(kind: pkDouble, double: s.readFloat64()) result = Preserve[E](kind: pkDouble, double: s.readFloat64())
else: else:
result = Preserve(kind: pkDouble) result = Preserve[E](kind: pkDouble)
var be = s.readFloat64() var be = s.readFloat64()
swapEndian64(result.double.addr, be.addr) swapEndian64(result.double.addr, be.addr)
of 0x86: of 0x86:
result = decodePreserves(s) result = decodePreserves(s, E)
result.embedded = true result.embedded = true
of 0xb1: of 0xb1:
result = Preserve(kind: pkString) result = Preserve[E](kind: pkString)
let len = s.readVarint() let len = s.readVarint()
result.string = s.readStr(len) result.string = s.readStr(len)
of 0xb2: of 0xb2:
result = Preserve(kind: pkByteString) result = Preserve[E](kind: pkByteString)
let len = s.readVarint() let len = s.readVarint()
result.bytes = cast[seq[byte]](s.readStr(len)) result.bytes = cast[seq[byte]](s.readStr(len))
of 0xb3: of 0xb3:
let len = s.readVarint() let len = s.readVarint()
result = Preserve(kind: pkSymbol, symbol: s.readStr(len)) result = Preserve[E](kind: pkSymbol, symbol: s.readStr(len))
of 0xb4: of 0xb4:
result = Preserve(kind: pkRecord) result = Preserve[E](kind: pkRecord)
var label = decodePreserves(s) var label = decodePreserves(s, E)
while s.peekUint8() != endMarker: while s.peekUint8() != endMarker:
result.record.add decodePreserves(s) result.record.add decodePreserves(s, E)
result.record.add(move label) result.record.add(move label)
discard s.readUint8() discard s.readUint8()
of 0xb5: of 0xb5:
result = Preserve(kind: pkSequence) result = Preserve[E](kind: pkSequence)
while s.peekUint8() != endMarker: while s.peekUint8() != endMarker:
result.sequence.add decodePreserves(s) result.sequence.add decodePreserves(s, E)
discard s.readUint8() discard s.readUint8()
of 0xb6: of 0xb6:
result = Preserve(kind: pkSet) result = Preserve[E](kind: pkSet)
while s.peekUint8() != endMarker: while s.peekUint8() != endMarker:
incl(result, decodePreserves(s)) incl(result, decodePreserves(s, E))
discard s.readUint8() discard s.readUint8()
of 0xb7: of 0xb7:
result = Preserve(kind: pkDictionary) result = Preserve[E](kind: pkDictionary)
while s.peekUint8() != endMarker: while s.peekUint8() != endMarker:
result[decodePreserves(s)] = decodePreserves(s) result[decodePreserves(s, E)] = decodePreserves(s, E)
discard s.readUint8() discard s.readUint8()
of 0xb0: of 0xb0:
let len = s.readVarint() let len = s.readVarint()
result = Preserve(kind: pkBigInteger, bigint: initBigint 0) result = Preserve[E](kind: pkBigInteger, bigint: initBigint 0)
for _ in 1..len: for _ in 1..len:
result.bigint = (result.bigint shl 8) + s.readUint8().int32 result.bigint = (result.bigint shl 8) + s.readUint8().int32
of endMarker: of endMarker:
@ -539,29 +558,29 @@ proc decodePreserves*(s: Stream): Preserve =
case 0xf0 and tag case 0xf0 and tag
of 0x90: of 0x90:
var n = tag.BiggestInt var n = tag.BiggestInt
result = Preserve(kind: pkSignedInteger, result = Preserve[E](kind: pkSignedInteger,
int: n - (if n > 0x9c: 0xa0 else: 0x90)) int: n - (if n > 0x9c: 0xa0 else: 0x90))
of 0xa0: of 0xa0:
let len = (tag.int and 0x0f) + 1 let len = (tag.int and 0x0f) + 1
if len <= 8: if len <= 8:
result = Preserve(kind: pkSignedInteger, int: s.readUint8().BiggestInt) result = Preserve[E](kind: pkSignedInteger, int: s.readUint8().BiggestInt)
if (result.int and 0x80) != 0: result.int.dec(0x100) if (result.int and 0x80) != 0: result.int.dec(0x100)
for i in 1..<len: for i in 1..<len:
result.int = (result.int shl 8) or s.readUint8().BiggestInt result.int = (result.int shl 8) or s.readUint8().BiggestInt
else: else:
result = Preserve(kind: pkBigInteger) result = Preserve[E](kind: pkBigInteger)
for i in 0..<len: for i in 0..<len:
result.bigint = (result.bigint shl 8) + s.readUint8().int32 result.bigint = (result.bigint shl 8) + s.readUint8().int32
else: else:
assertStream(false) assertStream(false)
proc decodePreserves*(s: string): Preserve = proc decodePreserves*(s: string; E = void): Preserve[E] =
## Decode a string of binary-encoded Preserves. ## Decode a string of binary-encoded Preserves.
s.newStringStream.decodePreserves decodePreserves(s.newStringStream, E)
proc decodePreserves*(s: seq[byte]): Preserve = proc decodePreserves*(s: seq[byte]; E = void): Preserve[E] =
## Decode a byte-string of binary-encoded Preserves. ## Decode a byte-string of binary-encoded Preserves.
cast[string](s).decodePreserves decodePreserves(cast[string](s), E)
template record*(label: string) {.pragma.} template record*(label: string) {.pragma.}
## Serialize this object or tuple as a record. See ``toPreserve``. ## Serialize this object or tuple as a record. See ``toPreserve``.
@ -569,61 +588,77 @@ template record*(label: string) {.pragma.}
template unpreservable*() {.pragma.} template unpreservable*() {.pragma.}
## Pragma to forbid a type from being converted by ``toPreserve``. ## Pragma to forbid a type from being converted by ``toPreserve``.
proc toPreserve*[T](x: T): Preserve = proc toPreserve*[T](x: T; E = void): Preserve[E] =
## Serializes ``x`` to Preserves. Can be customized by defining ## Serializes ``x`` to Preserves. Can be customized by defining
## ``toPreserveHook(x: T)`` in the calling scope. ## ``toPreserveHook(x: T; E: typedesc)`` in the calling scope.
when (T is Preserve): result = x ## Any ``toPreserveHook`` that does not compile will be discarded;
elif compiles(toPreserveHook(x)): ## *Write tests for your hooks!*
result = toPreserveHook(x) when (T is Preserve[E]): result = x
elif compiles(toPreserveHook(x, E)):
result = toPreserveHook(x, E)
elif T is Bigint: elif T is Bigint:
result = Preserve(kind: pkBigInteger, bigint: x) result = Preserve[E](kind: pkBigInteger, bigint: x)
elif T is seq[byte]: elif T is seq[byte]:
result = Preserve(kind: pkByteString, bytes: x) result = Preserve[E](kind: pkByteString, bytes: x)
elif T is array | seq: elif T is array | seq:
result = Preserve(kind: pkSequence, sequence: newSeqOfCap[Preserve](x.len)) result = Preserve[E](kind: pkSequence, sequence: newSeqOfCap[Preserve[E]](x.len))
for v in x.items: result.sequence.add(toPreserve(v)) for v in x.items: result.sequence.add(toPreserve(v, E))
elif T is bool: elif T is bool:
result = Preserve(kind: pkBoolean, bool: x) result = Preserve[E](kind: pkBoolean, bool: x)
elif T is distinct: elif T is distinct:
result = toPreserve(x.distinctBase) result = toPreserve(x.distinctBase, E)
elif T is float: elif T is float:
result = Preserve(kind: pkFloat, float: x) result = Preserve[E](kind: pkFloat, float: x)
elif T is float64: elif T is float64:
result = Preserve(kind: pkDouble, double: x) result = Preserve[E](kind: pkDouble, double: x)
elif T is object | tuple: elif T is object | tuple:
when T.hasCustomPragma(unpreservable): {.fatal: "unpreservable type".} when T.hasCustomPragma(unpreservable): {.fatal: "unpreservable type".}
elif T.hasCustomPragma(record): elif T.hasCustomPragma(record):
result = Preserve(kind: pkRecord) result = Preserve[E](kind: pkRecord)
for _, f in x.fieldPairs: result.record.add(toPreserve(f)) for _, f in x.fieldPairs: result.record.add(toPreserve(f, E))
result.record.add(symbol(T.getCustomPragmaVal(record))) result.record.add(symbol[E](T.getCustomPragmaVal(record)))
else: else:
result = Preserve(kind: pkDictionary) result = Preserve[E](kind: pkDictionary)
for k, v in x.fieldPairs: for k, v in x.fieldPairs:
result[symbol(k)] = toPreserve(v) result[symbol[E](k)] = toPreserve(v, E)
elif T is Ordinal: elif T is Ordinal:
result = Preserve(kind: pkSignedInteger, int: x.ord.BiggestInt) result = Preserve[E](kind: pkSignedInteger, int: x.ord.BiggestInt)
elif T is ptr | ref: elif T is ptr | ref:
if system.`==`(x, nil): result = symbol("null") if system.`==`(x, nil): result = symbol[E]("null")
else: result = toPreserve(x[]) else: result = toPreserve(x[], E)
elif T is string: elif T is string:
result = Preserve(kind: pkString, string: x) result = Preserve[E](kind: pkString, string: x)
elif T is SomeInteger: elif T is SomeInteger:
result = Preserve(kind: pkSignedInteger, int: x.BiggestInt) result = Preserve[E](kind: pkSignedInteger, int: x.BiggestInt)
else: else:
raiseAssert("unpreservable type" & $T) raiseAssert("unpreservable type" & $T)
proc toPreserveHook*[T](set: HashSet[T]): Preserve = proc toPreserveHook*[A](pr: Preserve[A]; E: typedesc): Preserve[E] =
## Hook for converting ``Preserve`` values with different embedded types.
if pr.kind == pkEmbedded:
when E is void:
result = toPreserve(pr.embed, E)
else:
result = Preserve[E](pk: pr.kind, embed: (E)pr.embed)
else:
result = cast[Preserve[E]](pr)
proc toPreserveHook*[T](set: HashSet[T]; E: typedesc): Preserve[E] =
## Hook for preserving ``HashSet``. ## Hook for preserving ``HashSet``.
Preserve(kind: pkSet, set: set.map(toPreserve)) result = Preserve[E](kind: pkSet, set: newSeqOfCap[Preserve[E]](set.len))
for e in set: result.incl toPreserve(e, E)
proc toPreserveHook*[A, B](table: Table[A, B]|TableRef[A, B]): Preserve = proc toPreserveHook*[A, B](table: Table[A, B]|TableRef[A, B], E: typedesc): Preserve[E] =
## Hook for preserving ``Table``. ## Hook for preserving ``Table``.
result = initDictionary() result = initDictionary[E]()
for k, v in table.pairs: result[k.toPreserve] = v.toPreserve for k, v in table.pairs: result[toPreserve(k, E)] = toPreserve(v, E)
proc fromPreserve*[T](v: var T; pr: Preserve): bool = proc fromPreserve*[T, E](v: var T; pr: Preserve[E]): bool =
## Inplace version of `preserveTo`. Returns ``true`` on ## Inplace version of `preserveTo`. Returns ``true`` on
## a complete match, otherwise returns ``false``. ## a complete match, otherwise returns ``false``.
## Can be customized with `fromPreserveHook[E](x: T; var pr: Preserve[E]): bool`.
## Any ``fromPreserveHook`` that does not compile will be discarded;
## *Write tests for your hooks!*
# TODO: {.raises: [].} # TODO: {.raises: [].}
runnableExamples: runnableExamples:
import preserves, preserves/parse import preserves, preserves/parse
@ -637,6 +672,8 @@ proc fromPreserve*[T](v: var T; pr: Preserve): bool =
when T is Value: when T is Value:
v = pr v = pr
result = true result = true
elif T is E:
result = pr.embed
elif compiles(fromPreserveHook(v, pr)): elif compiles(fromPreserveHook(v, pr)):
result = fromPreserveHook(v, pr) result = fromPreserveHook(v, pr)
elif T is Bigint: elif T is Bigint:
@ -695,7 +732,7 @@ proc fromPreserve*[T](v: var T; pr: Preserve): bool =
for key, val in v.fieldPairs: for key, val in v.fieldPairs:
inc fieldCount inc fieldCount
for (pk, pv) in pr.dict.items: for (pk, pv) in pr.dict.items:
var sym = symbol(key) var sym = symbol[E](key)
if sym == pk: if sym == pk:
result = result and fromPreserve(val, pv) result = result and fromPreserve(val, pv)
break break
@ -706,7 +743,7 @@ proc fromPreserve*[T](v: var T; pr: Preserve): bool =
v = (T)pr.int v = (T)pr.int
result = true result = true
elif T is ref: elif T is ref:
if pr != symbol("null"): if pr != symbol[E]("null"):
new v new v
result = fromPreserve(v[], pr) result = fromPreserve(v[], pr)
elif T is string: elif T is string:
@ -735,13 +772,28 @@ proc preserveTo*(pr: Preserve; T: typedesc): Option[T] =
if fromPreserve(v, pr): if fromPreserve(v, pr):
result = some(move v) result = some(move v)
proc fromPreserveHook*[A,B,E](t: var Table[A,B]|TableRef[A,B]; pr: Preserve): bool = proc fromPreserveHook*[T, E](set: var HashSet[T]; pr: Preserve[E]): bool =
if pr.isDictionary: ## Hook for preserving ``HashSet``.
for k, v in pr.pairs: if pr.kind == pkSet:
t[preserveTo(k, A)] = preserveTo(v, B) result = true
result = true set.init(pr.set.len)
var e: T
for pe in pr.set:
result = fromPreserve(e, pe)
if not result: break
set.incl(move e)
proc concat(result: var string; pr: Preserve) = proc fromPreserveHook*[A,B,E](t: var (Table[A,B]|TableRef[A,B]); pr: Preserve[E]): bool =
if pr.isDictionary:
result = true
var a: A
var b: B
for (k, v) in pr.dict:
result = fromPreserve(a, k) and fromPreserve(b, v)
if not result: break
t[move a] = move b
proc concat[E](result: var string; pr: Preserve[E]) =
if pr.embedded: result.add("#!") if pr.embedded: result.add("#!")
case pr.kind: case pr.kind:
of pkBoolean: of pkBoolean:
@ -804,6 +856,20 @@ proc concat(result: var string; pr: Preserve) =
result.concat(value) result.concat(value)
inc i inc i
result.add('}') result.add('}')
of pkEmbedded:
when not E is void:
result.add("#!")
result.add($pr.embed)
proc `$`*(pr: Preserve): string = concat(result, pr) proc `$`*(pr: Preserve): string = concat(result, pr)
## Generate the textual representation of ``pr``. ## Generate the textual representation of ``pr``.
when isMainModule:
block:
var t: Table[int, string]
var pr = t.toPreserveHook(void)
assert fromPreserveHook(t, pr)
block:
var h: HashSet[string]
var pr = h.toPreserveHook(void)
assert fromPreserveHook(h, pr)

View File

@ -4,31 +4,31 @@
import std/[json, tables] import std/[json, tables]
import ../preserves import ../preserves
proc toPreserveHook*(js: JsonNode): Preserve = proc toPreserveHook*(js: JsonNode; E: typedesc): Preserve[E] =
case js.kind case js.kind
of JString: of JString:
result = Preserve(kind: pkString, string: js.str) result = Preserve[E](kind: pkString, string: js.str)
of JInt: of JInt:
result = Preserve(kind: pkSignedInteger, int: js.num) result = Preserve[E](kind: pkSignedInteger, int: js.num)
of JFloat: of JFloat:
result = Preserve(kind: pkDouble, double: js.fnum) result = Preserve[E](kind: pkDouble, double: js.fnum)
of JBool: of JBool:
result = case js.bval result = case js.bval
of false: symbol"false" of false: symbol[E]"false"
of true: symbol"true" of true: symbol[E]"true"
of JNull: of JNull:
result = symbol"null" result = symbol[E]"null"
of JObject: of JObject:
result = Preserve(kind: pkDictionary) result = Preserve[E](kind: pkDictionary)
for key, val in js.fields.pairs: for key, val in js.fields.pairs:
result[Preserve(kind: pkString, string: key)] = toPreserveHook(val) result[Preserve[E](kind: pkString, string: key)] = toPreserveHook(val, E)
of JArray: of JArray:
result = Preserve(kind: pkSequence, result = Preserve[E](kind: pkSequence,
sequence: newSeq[Preserve](js.elems.len)) sequence: newSeq[Preserve[E]](js.elems.len))
for i, e in js.elems: for i, e in js.elems:
result.sequence[i] = toPreserveHook(e) result.sequence[i] = toPreserveHook(e, E)
proc fromPreserveHook*(js: var JsonNode; prs: Preserve): bool = proc fromPreserveHook*[E](js: var JsonNode; prs: Preserve[E]): bool =
case prs.kind: case prs.kind:
of pkBoolean: of pkBoolean:
js = newJBool(prs.bool) js = newJBool(prs.bool)
@ -67,6 +67,16 @@ proc fromPreserveHook*(js: var JsonNode; prs: Preserve): bool =
else: return false else: return false
true true
proc toJsonHook*(pr: Preserve): JsonNode = proc toJsonHook*[E](pr: Preserve[E]): JsonNode =
if not fromPreserve(result, pr): if not fromPreserveHook(result, pr):
raise newException(ValueError, "cannot convert Preserves value to JSON") raise newException(ValueError, "cannot convert Preserves value to JSON")
proc fromJsonHook*[E](pr: var Preserve[E]; js: JsonNode) =
pr = toPreserveHook(js, E)
when isMainModule:
var js = JsonNode()
var pr = js.toPreserveHook(void)
assert fromPreserveHook(js, pr)
fromJsonHook(pr, js)
js = toJsonHook(pr)

View File

@ -6,14 +6,15 @@ import npeg
import ../preserves, ./pegs import ../preserves, ./pegs
type type
Frame = tuple[value: Preserve, pos: int] Value = Preserve[void]
Frame = tuple[value: Value, pos: int]
Stack = seq[Frame] Stack = seq[Frame]
proc shrink(stack: var Stack; n: int) = stack.setLen(stack.len - n) proc shrink(stack: var Stack; n: int) = stack.setLen(stack.len - n)
template pushStack(v: Preserve) = stack.add((v, capture[0].si)) template pushStack(v: Value) = stack.add((v, capture[0].si))
proc parsePreserves*(text: string): Preserve {.gcsafe.} = proc parsePreserves*(text: string): Preserve[void] {.gcsafe.} =
const pegParser = peg("Document", stack: Stack): const pegParser = peg("Document", stack: Stack):
# Override rules from pegs.nim # Override rules from pegs.nim
@ -21,7 +22,7 @@ proc parsePreserves*(text: string): Preserve {.gcsafe.} =
Preserves.Record <- Preserves.Record: Preserves.Record <- Preserves.Record:
var var
record: seq[Preserve] record: seq[Value]
labelOff: int labelOff: int
while stack[labelOff].pos < capture[0].si: while stack[labelOff].pos < capture[0].si:
inc labelOff inc labelOff
@ -29,18 +30,18 @@ proc parsePreserves*(text: string): Preserve {.gcsafe.} =
record.add(move stack[i].value) record.add(move stack[i].value)
record.add(move stack[labelOff].value) record.add(move stack[labelOff].value)
stack.shrink record.len stack.shrink record.len
pushStack Preserve(kind: pkRecord, record: move record) pushStack Value(kind: pkRecord, record: move record)
Preserves.Sequence <- Preserves.Sequence: Preserves.Sequence <- Preserves.Sequence:
var sequence: seq[Preserve] var sequence: seq[Value]
for frame in stack.mitems: for frame in stack.mitems:
if frame.pos > capture[0].si: if frame.pos > capture[0].si:
sequence.add(move frame.value) sequence.add(move frame.value)
stack.shrink sequence.len stack.shrink sequence.len
pushStack Preserve(kind: pkSequence, sequence: move sequence) pushStack Value(kind: pkSequence, sequence: move sequence)
Preserves.Dictionary <- Preserves.Dictionary: Preserves.Dictionary <- Preserves.Dictionary:
var prs = Preserve(kind: pkDictionary) var prs = Value(kind: pkDictionary)
for i in countDown(stack.high.pred, 0, 2): for i in countDown(stack.high.pred, 0, 2):
if stack[i].pos < capture[0].si: break if stack[i].pos < capture[0].si: break
prs[move stack[i].value] = stack[i.succ].value prs[move stack[i].value] = stack[i.succ].value
@ -48,7 +49,7 @@ proc parsePreserves*(text: string): Preserve {.gcsafe.} =
pushStack prs pushStack prs
Preserves.Set <- Preserves.Set: Preserves.Set <- Preserves.Set:
var prs = Preserve(kind: pkSet) var prs = Value(kind: pkSet)
for frame in stack.mitems: for frame in stack.mitems:
if frame.pos > capture[0].si: if frame.pos > capture[0].si:
prs.incl(move frame.value) prs.incl(move frame.value)
@ -57,36 +58,36 @@ proc parsePreserves*(text: string): Preserve {.gcsafe.} =
Preserves.Boolean <- Preserves.Boolean: Preserves.Boolean <- Preserves.Boolean:
case $0 case $0
of "#f": pushStack Preserve(kind: pkBoolean) of "#f": pushStack Value(kind: pkBoolean)
of "#t": pushStack Preserve(kind: pkBoolean, bool: true) of "#t": pushStack Value(kind: pkBoolean, bool: true)
else: discard else: discard
Preserves.Float <- Preserves.Float: Preserves.Float <- Preserves.Float:
pushStack Preserve(kind: pkFloat, float: parseFloat($1)) pushStack Value(kind: pkFloat, float: parseFloat($1))
Preserves.Double <- Preserves.Double: Preserves.Double <- Preserves.Double:
pushStack Preserve(kind: pkDouble) pushStack Value(kind: pkDouble)
let i = stack.high let i = stack.high
discard parseBiggestFloat($0, stack[i].value.double) discard parseBiggestFloat($0, stack[i].value.double)
Preserves.SignedInteger <- Preserves.SignedInteger: Preserves.SignedInteger <- Preserves.SignedInteger:
pushStack Preserve(kind: pkSignedInteger, int: parseInt($0)) pushStack Value(kind: pkSignedInteger, int: parseInt($0))
Preserves.String <- Preserves.String: Preserves.String <- Preserves.String:
pushStack Preserve(kind: pkString, string: unescape($0)) pushStack Value(kind: pkString, string: unescape($0))
Preserves.charByteString <- Preserves.charByteString: Preserves.charByteString <- Preserves.charByteString:
let s = unescape($1) let s = unescape($1)
pushStack Preserve(kind: pkByteString, bytes: cast[seq[byte]](s)) pushStack Value(kind: pkByteString, bytes: cast[seq[byte]](s))
Preserves.hexByteString <- Preserves.hexByteString: Preserves.hexByteString <- Preserves.hexByteString:
pushStack Preserve(kind: pkByteString, bytes: cast[seq[byte]](parseHexStr($1))) pushStack Value(kind: pkByteString, bytes: cast[seq[byte]](parseHexStr($1)))
Preserves.b64ByteString <- Preserves.b64ByteString: Preserves.b64ByteString <- Preserves.b64ByteString:
pushStack Preserve(kind: pkByteString, bytes: cast[seq[byte]](base64.decode($1))) pushStack Value(kind: pkByteString, bytes: cast[seq[byte]](base64.decode($1)))
Preserves.Symbol <- Preserves.Symbol: Preserves.Symbol <- Preserves.Symbol:
pushStack Preserve(kind: pkSymbol, symbol: $0) pushStack Value(kind: pkSymbol, symbol: $0)
Preserves.Embedded <- Preserves.Embedded: Preserves.Embedded <- Preserves.Embedded:
var v = stack.pop.value var v = stack.pop.value
@ -94,7 +95,7 @@ proc parsePreserves*(text: string): Preserve {.gcsafe.} =
pushStack v pushStack v
Preserves.Compact <- Preserves.Compact: Preserves.Compact <- Preserves.Compact:
pushStack decodePreserves(stack.pop.value.bytes) pushStack decodePreserves(stack.pop.value.bytes, void)
var stack: Stack var stack: Stack
let match = pegParser.match(text, stack) let match = pegParser.match(text, stack)

View File

@ -1,13 +1,16 @@
# SPDX-FileCopyrightText: 2021 ☭ Emery Hemingway # SPDX-FileCopyrightText: 2021 ☭ Emery Hemingway
# SPDX-License-Identifier: Unlicense # SPDX-License-Identifier: Unlicense
import std/[os, strutils, tables] import std/[os, strutils, sets, tables]
import compiler/[ast, idents, renderer, lineinfos] import compiler/[ast, idents, renderer, lineinfos]
import ../../preserves, ../schemas import ../../preserves, ../schemas
type TypeTable = OrderedTable[string, PNode] type
Value = Preserve[void]
TypeSpec = tuple[node: PNode, embeddable: bool]
TypeTable = OrderedTable[string, TypeSpec]
proc add(parent, child: PNode): PNode {.discardable.} = proc add(parent, child: PNode): PNode {.discardable.} =
parent.sons.add child parent.sons.add child
@ -52,31 +55,50 @@ proc ident(sn: SchemaNode): PNode =
raiseAssert("no ident for " & $sn.kind & " " & $sn) raiseAssert("no ident for " & $sn.kind & " " & $sn)
s.ident.accQuote s.ident.accQuote
proc typeIdent(sn: SchemaNode): PNode = proc parameterize(node: PNode; embeddable: bool): PNode =
if embeddable: nn(nkBracketExpr, node, ident"E")
else: node
proc parameterize(spec: TypeSpec): PNode =
parameterize(spec.node, spec.embeddable)
proc isPreserve(n: PNode): bool =
n.kind == nkBracketExpr and
n.renderTree == "Preserve[E]"
proc preserveIdent(): Pnode =
nn(nkBracketExpr, ident"Preserve", ident"E")
proc orEmbed(x: var TypeSpec; y: TypeSpec) =
x.embeddable = x.embeddable or y.embeddable
proc isEmbeddable(scm: Schema; sn: SchemaNode; seen: var HashSet[string]): bool =
case sn.kind case sn.kind
of snkAtom: of snkAtom, snkLiteral: discard
case sn.atom of snkAlt:
of akBool: ident"bool" result = isEmbeddable(scm, sn.altBranch, seen)
of akFloat: ident"float32" of snkAny: result = true
of akDouble: ident"float64"
of akInt: ident"BiggestInt"
of akString: ident"string"
of akBytes: nn(nkBracketExpr, ident"seq", ident"byte")
of akSymbol: ident"string" # TODO: distinct string type for symbols?
of snkNamed:
sn.pattern.typeIdent
of snkRef: of snkRef:
var id = ident sn.refPath[sn.refPath.high] if sn.refPath.len == 1:
for i in countDown(sn.refPath.high.pred, 0): let name = sn.refPath[0]
id = nn(nkDotExpr, ident(sn.refPath[i]), id) if name notin seen: # loop detection
id seen.incl name
result = isEmbeddable(scm, scm.definitions[name], seen)
else:
result = false
# TODO: cross-module types
of snkEmbedded:
result = isEmbeddable(scm, sn.embed, seen)
of snkNamed:
result = isEmbeddable(scm, sn.pattern, seen)
else: else:
ident"Preserve" for bn in sn.nodes:
result = isEmbeddable(scm, bn, seen)
if result: break
proc toExport(n: sink PNode): PNode = proc isEmbeddable(scm: Schema; sn: SchemaNode): bool =
nkPostFix.newNode.add(ident"*", n) var seen: HashSet[string]
isEmbeddable(scm, sn, seen)
proc newEmpty(): PNode = newNode(nkEmpty)
proc isConst(scm: Schema; sn: SchemaNode): bool = proc isConst(scm: Schema; sn: SchemaNode): bool =
case sn.kind case sn.kind
@ -86,7 +108,46 @@ proc isConst(scm: Schema; sn: SchemaNode): bool =
result = isConst(scm, scm.definitions[sn.refPath[0]]) result = isConst(scm, scm.definitions[sn.refPath[0]])
else: discard else: discard
proc literal(scm: Schema; sn: SchemaNode): Preserve = proc isSymbolEnum(scm: Schema; sn: SchemaNode): bool =
case sn.kind
of snkRef:
if sn.refPath.len == 1:
result = isSymbolEnum(scm, scm.definitions[sn.refPath[0]])
of snkOr:
for bn in sn.nodes:
if bn.altBranch.kind != snkLiteral or
bn.altBranch.value.kind != pkSymbol:
return false
result = true
else: discard
proc typeIdent(scm: Schema; sn: SchemaNode): TypeSpec =
case sn.kind
of snkAtom:
case sn.atom
of akBool: (ident"bool", false)
of akFloat: (ident"float32", false)
of akDouble: (ident"float64", false)
of akInt: (ident"BiggestInt", false)
of akString: (ident"string", false)
of akBytes: (nn(nkBracketExpr, ident"seq", ident"byte"), false)
of akSymbol: (ident"string", false) # TODO: distinct string type for symbols?
of snkNamed:
typeIdent(scm, sn.pattern)
of snkRef:
var id = ident sn.refPath[sn.refPath.high]
for i in countDown(sn.refPath.high.pred, 0):
id = nn(nkDotExpr, ident(sn.refPath[i]), id)
(id, isEmbeddable(scm, sn))
else:
(preserveIdent(), true)
proc toExport(n: sink PNode): PNode =
nkPostFix.newNode.add(ident"*", n)
proc newEmpty(): PNode = newNode(nkEmpty)
proc literal(scm: Schema; sn: SchemaNode): Value =
case sn.kind case sn.kind
of snkLiteral: result = sn.value of snkLiteral: result = sn.value
of snkRef: of snkRef:
@ -97,14 +158,6 @@ proc literal(scm: Schema; sn: SchemaNode): Preserve =
else: else:
raiseAssert("not convertable to a literal: " & $sn) raiseAssert("not convertable to a literal: " & $sn)
proc isSymbolEnum(sn: SchemaNode): bool =
if sn.kind == snkOr:
for bn in sn.nodes:
if bn.altBranch.kind != snkLiteral or
bn.altBranch.value.kind != pkSymbol:
return false
result = true
proc toEnumTy(sn: SchemaNode): PNode = proc toEnumTy(sn: SchemaNode): PNode =
result = nkEnumTy.newNode.add newEmpty() result = nkEnumTy.newNode.add newEmpty()
for bn in sn.nodes: for bn in sn.nodes:
@ -118,7 +171,22 @@ proc toEnumDef(name: string; sn: SchemaNode): PNode =
newEmpty(), newEmpty(),
sn.toEnumTy) sn.toEnumTy)
proc typeDef(sn: SchemaNode; name: string; ty: PNode): PNode = proc embeddingParams(embeddable: bool): PNode =
if embeddable:
nn(nkGenericParams, nn(nkIdentDefs, ident"E", newEmpty(), ident"void"))
else:
newEmpty()
proc identDef(sn: SchemaNode; a, b: PNode; embeddable: bool): PNode =
if embeddable and b.kind != nkBracketExpr:
nn(nkIdentDefs, a, nn(nkBracketExpr, b, ident"E"), newEmpty())
else:
nn(nkIdentDefs, a, b, newEmpty())
proc identDef(sn: SchemaNode; l: PNode; ts: TypeSpec): PNode =
identDef(sn, l, ts.node, ts.embeddable)
proc typeDef(sn: SchemaNode; name: string; ty: PNode; embeddable: bool): PNode =
case sn.kind case sn.kind
of snkRecord: of snkRecord:
nn(nkTypeDef, nn(nkTypeDef,
@ -128,22 +196,24 @@ proc typeDef(sn: SchemaNode; name: string; ty: PNode): PNode =
nn(nkExprColonExpr, nn(nkExprColonExpr,
ident"record", ident"record",
PNode(kind: nkStrLit, strVal: sn.nodes[0].value.symbol)))), PNode(kind: nkStrLit, strVal: sn.nodes[0].value.symbol)))),
newEmpty(), embeddingParams(embeddable),
ty) ty)
else: else:
nn(nkTypeDef, name.ident.toExport, newEmpty(), ty) nn(nkTypeDef, name.ident.toExport, embeddingParams(embeddable), ty)
proc nimTypeOf(scm: Schema; known: var TypeTable; sn: SchemaNode; name = ""): PNode = proc nimTypeOf(scm: Schema; known: var TypeTable; sn: SchemaNode; name = ""):
TypeSpec =
if name in known: return known[name]
case sn.kind case sn.kind
of snkOr: of snkOr:
if sn.isSymbolEnum: if isSymbolEnum(scm, sn):
result = sn.toEnumTy result.node = sn.toEnumTy
else: else:
let let
enumName = name.nimIdentNormalize & "Kind" enumName = name.nimIdentNormalize & "Kind"
enumIdent = ident(enumName) enumIdent = ident(enumName)
if enumName notin known: if enumName notin known:
known[enumName] = toEnumDef(enumName, sn) known[enumName] = (toEnumDef(enumName, sn), false)
let recCase = nkRecCase.newNode.add( let recCase = nkRecCase.newNode.add(
nkIdentDefs.newNode.add( nkIdentDefs.newNode.add(
"kind".ident.toExport, "kind".ident.toExport,
@ -152,15 +222,19 @@ proc nimTypeOf(scm: Schema; known: var TypeTable; sn: SchemaNode; name = ""): PN
for bn in sn.nodes: for bn in sn.nodes:
assert(bn.kind == snkAlt, $bn.kind) assert(bn.kind == snkAlt, $bn.kind)
doAssert(name != "", " no name for " & $sn) doAssert(name != "", " no name for " & $sn)
var memberTypeIdent: PNode var memberType: TypeSpec
if bn.altbranch.kind == snkRef: if bn.altbranch.kind == snkRef:
memberTypeIdent = bn.altBranch.typeIdent memberType = typeIdent(scm, bn.altBranch)
else: else:
let memberTypeName = name & bn.altLabel.nimIdentNormalize let memberTypeName = name & bn.altLabel.nimIdentNormalize
memberTypeIdent = ident memberTypeName memberType.node = ident memberTypeName
if memberTypeName notin known: if memberTypeName notin known:
let ty = nimTypeOf(scm, known, bn.altBranch, memberTypeName) let ty = nimTypeOf(scm, known, bn.altBranch, memberTypeName)
known[memberTypeName] = typeDef(bn.altBranch, memberTypeName, ty) orEmbed memberType, ty
orEmbed result, memberType
known[memberTypeName] = (
typeDef(bn.altBranch, memberTypeName, ty.node, ty.embeddable),
ty.embeddable)
var recList = nkRecList.newNode var recList = nkRecList.newNode
case bn.altBranch.kind case bn.altBranch.kind
of snkRecord: of snkRecord:
@ -169,44 +243,36 @@ proc nimTypeOf(scm: Schema; known: var TypeTable; sn: SchemaNode; name = ""): PN
of 2: of 2:
if not isConst(scm, bn.altBranch.nodes[1]): if not isConst(scm, bn.altBranch.nodes[1]):
let label = bn.ident let label = bn.ident
recList.add nkIdentDefs.newNode.add( let branch = typeIdent(scm, bn.altBranch.nodes[1])
label.toExport, orEmbed result, branch
nimTypeOf(scm, known, bn.altBranch.nodes[1], $label), recList.add identDef(bn, label.toExport, branch)
newEmpty())
else: else:
recList.add nkIdentDefs.newNode.add( recList.add identDef(bn, bn.ident.toExport, memberType)
bn.ident.toExport,
memberTypeIdent,
newEmpty())
else: else:
if isConst(scm, bn.altBranch): if isConst(scm, bn.altBranch):
recList.add nkDiscardStmt.newNode.add(newEmpty()) recList.add nkDiscardStmt.newNode.add(newEmpty())
else: else:
let label = bn.ident let label = bn.ident
recList.add(nkIdentDefs.newNode.add( let branch = typeIdent(scm, bn.altBranch)
label.toExport, orEmbed result, branch
nimTypeOf(scm, known, bn.altBranch, $label), recList.add identDef(bn, label.toExport, branch)
newEmpty()))
let disc = nkDotExpr.newNode.add( let disc = nkDotExpr.newNode.add(
enumIdent, bn.altLabel.nimIdentNormalize.ident.accQuote) enumIdent, bn.altLabel.nimIdentNormalize.ident.accQuote)
if recList.len == 0: if recList.len == 0:
recList.add nkIdentDefs.newNode.add( recList.add identDef(bn, bn.ident.toExport, memberType)
bn.ident.toExport,
memberTypeIdent,
newEmpty())
recCase.add nkOfBranch.newNode.add(disc, recList) recCase.add nkOfBranch.newNode.add(disc, recList)
result = nn(nkRefTy, nn(nkObjectTy, result.node = nn(nkRefTy, nn(nkObjectTy,
newEmpty(), newEmpty(),
newEmpty(), newEmpty(),
nn(nkRecList, recCase))) nn(nkRecList, recCase)))
of snkAny: of snkAny:
result = ident"Preserve" result = (ident"Preserve", true)
of snkAtom: of snkAtom:
result = typeIdent(sn) result = typeIdent(scm, sn)
of snkEmbedded: of snkEmbedded:
result = nimTypeOf(scm, known, sn.embed) result = nimTypeOf(scm, known, sn.embed)
of snkLiteral: of snkLiteral:
result = case sn.value.kind # nearly verbatim from ../../preserves/src/preserves.nim result.node = case sn.value.kind # nearly verbatim from ../../preserves/src/preserves.nim
of pkBoolean: ident"bool" of pkBoolean: ident"bool"
of pkFloat: ident"float32" of pkFloat: ident"float32"
of pkDouble: ident"float64" of pkDouble: ident"float64"
@ -216,30 +282,37 @@ proc nimTypeOf(scm: Schema; known: var TypeTable; sn: SchemaNode; name = ""): PN
of pkByteString: nn( of pkByteString: nn(
nkBracketExpr, ident"seq", ident"byte") nkBracketExpr, ident"seq", ident"byte")
of pkSymbol: ident"string" of pkSymbol: ident"string"
of pkRecord: ident"Preserve" of pkRecord: preserveIdent()
of pkSequence: nn( of pkSequence: nn(
nkBracketExpr, ident"seq", ident"Preserve") nkBracketExpr, ident"seq", preserveIdent())
of pkSet: nn( of pkSet: nn(
nkBracketExpr, ident"HashSet", ident"Preserve") nkBracketExpr, ident"HashSet", preserveIdent())
of pkDictionary: nn( of pkDictionary: nn(
nkBracketExpr, ident"Table", ident"Preserve", ident"Preserve") nkBracketExpr, ident"TableRef", preserveIdent(), preserveIdent())
of pkEmbedded:
raiseAssert "this should never happen"
of snkSequenceOf: of snkSequenceOf:
result = nkBracketExpr.newNode.add( result = nimTypeOf(scm, known, sn.child)
result.node = nkBracketExpr.newNode.add(
ident"seq", ident"seq",
nimTypeOf(scm, known, sn.child)) parameterize(result))
of snkSetOf: of snkSetOf:
result = nkBracketExpr.newNode.add( result = nimTypeOf(scm, known, sn.child)
result.node = nkBracketExpr.newNode.add(
ident"HashedSet", ident"HashedSet",
nimTypeOf(scm, known, sn.child)) parameterize(result))
of snkDictOf: of snkDictOf:
result = nkBracketExpr.newNode.add( let
ident"Table", key = nimTypeOf(scm, known, sn.nodes[0])
nimTypeOf(scm, known, sn.nodes[0]), val = nimTypeOf(scm, known, sn.nodes[1])
nimTypeOf(scm, known, sn.nodes[1])) orEmbed result, key
orEmbed result, val
result.node = nkBracketExpr.newNode.add(
ident"TableRef", parameterize(key), parameterize(val))
of snkRecord: of snkRecord:
case sn.nodes.len case sn.nodes.len
of 0, 1: of 0, 1:
result = nn(nkObjectTy, result.node = nn(nkObjectTy,
newEmpty(), newEmpty(),
newEmpty(), newEmpty(),
nn(nkRecList, nn(nkDiscardStmt, newEmpty()))) nn(nkRecList, nn(nkDiscardStmt, newEmpty())))
@ -247,62 +320,55 @@ proc nimTypeOf(scm: Schema; known: var TypeTable; sn: SchemaNode; name = ""): PN
let recList = nkRecList.newNode() let recList = nkRecList.newNode()
for i, field in sn.nodes: for i, field in sn.nodes:
if i > 0: if i > 0:
let id = field.ident let
recList.add nkIdentDefs.newNode.add( id = field.ident
id.toExport, fieldType = nimTypeOf(scm, known, field, $id)
nimTypeOf(scm, known, field, $id), orEmbed result, fieldType
newEmpty()) recList.add identDef(sn, id.toExport, fieldType)
result = nn(nkRefTy, nn(nkObjectTy, result.node = nn(nkRefTy, nn(nkObjectTy,
newEmpty(), newEmpty(),
newEmpty(), newEmpty(),
recList)) recList))
of snkTuple: of snkTuple:
# TODO: the variable part result.node = nkTupleTy.newNode
result = nkTupleTy.newNode
for tn in sn.nodes: for tn in sn.nodes:
if not isConst(scm, sn): if not isConst(scm, sn):
result.add nkIdentDefs.newNode.add( let fieldType = nimTypeOf(scm, known, tn)
tn.ident, nimTypeOf(scm, known, tn), newEmpty()) orEmbed result, fieldType
result.node.add identDef(sn, tn.ident, fieldType)
of snkVariableTuple: of snkVariableTuple:
result = nkTupleTy.newNode result.node = nkTupleTy.newNode
for i, tn in sn.nodes: for i, tn in sn.nodes:
if not isConst(scm, sn): if not isConst(scm, sn):
let fieldType = nimTypeOf(scm, known, tn)
orEmbed result, fieldType
if i == sn.nodes.high: if i == sn.nodes.high:
result.add nkIdentDefs.newNode.add( result.node.add identDef(
tn,
tn.ident, tn.ident,
nn(nkBracketExpr, ident"seq", nimTypeOf(scm, known, tn)), nn(nkBracketExpr, ident"seq", fieldType.node),
newEmpty()) fieldType.embeddable)
else: else:
result.add nkIdentDefs.newNode.add( result.node.add identDef(tn, tn.ident, fieldType)
tn.ident, nimTypeOf(scm, known, tn), newEmpty())
of snkDictionary: of snkDictionary:
result = nkTupleTy.newNode result.node = nkTupleTy.newNode
for i in countup(0, sn.nodes.high, 2): for i in countup(0, sn.nodes.high, 2):
let id = ident(sn.nodes[i+0]) let id = ident(sn.nodes[i+0])
result.add nkIdentDefs.newNode.add( let fieldType = nimTypeOf(scm, known, sn.nodes[i+1], $id)
id, orEmbed result, fieldType
nimTypeOf(scm, known, sn.nodes[i+1], $id), result.node.add identDef(sn.nodes[i+1], id, fieldType)
newEmpty())
of snkNamed: of snkNamed:
result = nimTypeOf(scm, known, sn.pattern, name) result = nimTypeOf(scm, known, sn.pattern, name)
of snkRef: of snkRef:
if sn.refPath.len == 1: result = typeIdent(scm, sn)
let
refName = sn.refPath[0]
refDef = scm.definitions[refName]
case refDef.kind
of snkDictOf:
result = nimTypeOf(scm, known, refDef, refName)
else: result = typeIdent(sn)
else:
result = typeIdent(sn)
else: else:
result = nkCommentStmt.newNode result.node = nkCommentStmt.newNode
result.comment = result.comment & "Missing type generator for " & $sn.kind & " " & $sn result.node.comment = result.node.comment &
"Missing type generator for " & $sn.kind & " " & $sn
proc exportIdent(id: string): PNode = nn(nkPostFix, ident"*", ident(id)) proc exportIdent(id: string): PNode = nn(nkPostFix, ident"*", ident(id))
proc generateConstProcs(result: var seq[PNode]; name: string; def: SchemaNode) = proc generateConstProcs(result: var seq[PNode]; scm: Schema, name: string; def: SchemaNode) =
case def.kind case def.kind
of snkLiteral: of snkLiteral:
var stmts = nn(nkStmtList) var stmts = nn(nkStmtList)
@ -311,7 +377,7 @@ proc generateConstProcs(result: var seq[PNode]; name: string; def: SchemaNode) =
discard stmts.add newIntNode(nkIntLit, def.value.int) discard stmts.add newIntNode(nkIntLit, def.value.int)
of pkSymbol: of pkSymbol:
discard stmts.add nn(nkCall, discard stmts.add nn(nkCall,
ident"symbol", nn(nkBracketExpr, ident"symbol", ident"E"),
PNode(kind: nkStrLit, strVal: def.value.symbol)) PNode(kind: nkStrLit, strVal: def.value.symbol))
else: else:
raiseAssert("conversion of " & $def & " to a Nim literal is not implemented") raiseAssert("conversion of " & $def & " to a Nim literal is not implemented")
@ -320,8 +386,8 @@ proc generateConstProcs(result: var seq[PNode]; name: string; def: SchemaNode) =
let constProc= nn(nkProcDef, let constProc= nn(nkProcDef,
exportIdent(procId), exportIdent(procId),
newEmpty(), newEmpty(),
newEmpty(), nn(nkGenericParams, nn(nkIdentDefs, ident"E", newEmpty(), ident"void")),
nn(nkFormalParams, ident"Preserve"), nn(nkFormalParams, preserveIdent()),
newEmpty(), newEmpty(),
newEmpty(), newEmpty(),
stmts) stmts)
@ -329,18 +395,18 @@ proc generateConstProcs(result: var seq[PNode]; name: string; def: SchemaNode) =
result.add constProc result.add constProc
else: discard else: discard
proc toNimLit(sn: SchemaNode): PNode = proc nimLit(scm: Schema; sn: SchemaNode): PNode =
assert(sn.kind == snkLiteral, $sn) assert(sn.kind == snkLiteral, $sn)
case sn.value.kind case sn.value.kind
of pkSymbol: of pkSymbol:
nkCall.newNode.add( nn(nkCall,
ident"symbol", nn(nkBracketExpr, ident"symbol", ident"E"),
PNode(kind: nkStrLit, strVal: sn.value.symbol)) PNode(kind: nkStrLit, strVal: sn.value.symbol))
else: else:
raiseAssert("no Nim literal for " & $sn) raiseAssert("no Nim literal for " & $sn)
proc literalToPreserveCall(pr: Preserve): PNode = proc literalToPreserveCall(pr: Preserve): PNode =
var prConstr = nn(nkObjConstr, ident"Preserve") var prConstr = nn(nkObjConstr, preserveIdent())
proc constr(kind, field: string; lit: PNode) = proc constr(kind, field: string; lit: PNode) =
prConstr.add nn(nkExprColonExpr, ident"kind", ident(kind)) prConstr.add nn(nkExprColonExpr, ident"kind", ident(kind))
prConstr.add nn(nkExprColonExpr, ident(field), lit) prConstr.add nn(nkExprColonExpr, ident(field), lit)
@ -370,8 +436,7 @@ proc tupleConstructor(scm: Schema; sn: SchemaNode; ident: PNode): Pnode =
seqBracket.add literalToPreserveCall(literal(scm, field)) seqBracket.add literalToPreserveCall(literal(scm, field))
elif sn.kind == snkTuple or i < sn.nodes.high: elif sn.kind == snkTuple or i < sn.nodes.high:
seqBracket.add nn(nkCall, seqBracket.add nn(nkCall,
ident"toPreserve", ident"toPreserve", nn(nkDotExpr, ident, field.ident), ident"E")
nn(nkDotExpr, ident, field.ident))
let seqConstr = nn(nkPrefix, ident"@", seqBracket) let seqConstr = nn(nkPrefix, ident"@", seqBracket)
let colonExpr = nn(nkExprColonExpr, ident"sequence") let colonExpr = nn(nkExprColonExpr, ident"sequence")
if sn.kind == snkTuple: if sn.kind == snkTuple:
@ -383,10 +448,11 @@ proc tupleConstructor(scm: Schema; sn: SchemaNode; ident: PNode): Pnode =
nn(nkDotExpr, nn(nkDotExpr,
nn(nkCall, ident"toPreserve", nn(nkCall, ident"toPreserve",
nn(nkDotExpr, nn(nkDotExpr,
ident, sn.nodes[sn.nodes.high].ident)), ident, sn.nodes[sn.nodes.high].ident),
ident"E"),
ident"sequence")) ident"sequence"))
nn(nkObjConstr, nn(nkObjConstr,
ident"Preserve", preserveIdent(),
nn(nkExprColonExpr, ident"kind", ident"pkSequence"), nn(nkExprColonExpr, ident"kind", ident"pkSequence"),
colonExpr) colonExpr)
@ -397,7 +463,7 @@ proc generateProcs(result: var seq[PNode]; scm: Schema; name: string; sn: Schema
enumId = name.ident enumId = name.ident
paramId = ident"v" paramId = ident"v"
orStmts = nn(nkStmtList) orStmts = nn(nkStmtList)
if sn.isSymbolEnum: if isSymbolEnum(scm, sn):
let caseStmt = nn(nkCaseStmt, paramId) let caseStmt = nn(nkCaseStmt, paramId)
for bn in sn.nodes: for bn in sn.nodes:
caseStmt.add nn(nkOfBranch, caseStmt.add nn(nkOfBranch,
@ -405,7 +471,8 @@ proc generateProcs(result: var seq[PNode]; scm: Schema; name: string; sn: Schema
enumId, enumId,
bn.altLabel.nimIdentNormalize.ident.accQuote), bn.altLabel.nimIdentNormalize.ident.accQuote),
nn(nkCall, nn(nkCall,
ident"symbol", PNode(kind: nkStrLit, strVal: $bn.altLabel))) nn(nkBracketExpr, ident"symbol", ident"E"),
PNode(kind: nkStrLit, strVal: $bn.altLabel)))
orStmts.add caseStmt orStmts.add caseStmt
else: else:
let caseStmt = nn(nkCaseStmt, nn(nkDotExpr, paramId, ident"kind")) let caseStmt = nn(nkCaseStmt, nn(nkDotExpr, paramId, ident"kind"))
@ -420,13 +487,13 @@ proc generateProcs(result: var seq[PNode]; scm: Schema; name: string; sn: Schema
else: else:
stmts.add nn(nkCall, stmts.add nn(nkCall,
ident"toPreserve", ident"toPreserve",
nn(nkDotExpr, paramId, fieldId)) nn(nkDotExpr, paramId, fieldId), ident"E")
of snkTuple, snkVariableTuple: of snkTuple, snkVariableTuple:
stmts.add tupleConstructor(scm, sn, nn(nkDotExpr, paramId, fieldId)) stmts.add tupleConstructor(scm, sn, nn(nkDotExpr, paramId, fieldId))
of snkAtom, snkSequenceOf: of snkAtom, snkSequenceOf:
stmts.add nn(nkCall, stmts.add nn(nkCall,
ident"toPreserve", ident"toPreserve",
nn(nkDotExpr, paramId, fieldId)) nn(nkDotExpr, paramId, fieldId), ident"E")
else: else:
raiseAssert("no case statement for " & $sn.kind & " " & $sn) raiseAssert("no case statement for " & $sn.kind & " " & $sn)
for bn in sn.nodes: for bn in sn.nodes:
@ -443,41 +510,43 @@ proc generateProcs(result: var seq[PNode]; scm: Schema; name: string; sn: Schema
newEmpty(), newEmpty(),
newEmpty(), newEmpty(),
nn(nkFormalParams, nn(nkFormalParams,
ident"Preserve", preserveIdent(),
nn(nkIdentDefs, nn(nkIdentDefs,
paramId, ident(name), newEmpty())), paramId, ident(name), newEmpty()),
nn(nkIdentDefs,
ident"E", ident"typedesc", newEmpty())),
newEmpty(), newEmpty(),
newEmpty(), newEmpty(),
orStmts) orStmts)
of snkRecord: of snkRecord:
var var
params = nn(nkFormalParams, ident"Preserve") params = nn(nkFormalParams, preserveIdent())
initRecordCall = nn(nkCall, initRecordCall = nn(nkCall,
ident"initRecord", nn(nkBracketExpr, ident"initRecord", ident"E"),
sn.nodes[0].toNimLit) nimLit(scm, sn.nodes[0]))
for i, field in sn.nodes: for i, field in sn.nodes:
if i > 0: if i > 0:
let let
id = field.ident id = field.ident
var fieldType = field.typeIdent var (fieldType, embeddable) = typeIdent(scm, field)
if fieldType.kind != nkIdent or fieldType.ident.s != "Preserve": if not fieldType.isPreserve:
fieldType = fieldType =
nn(nkInfix, nn(nkInfix,
ident"|", ident"|",
fieldType, fieldType,
ident"Preserve") preserveIdent())
params.add nn(nkIdentDefs, params.add nn(nkIdentDefs,
id, fieldType, newEmpty()) id, fieldType, newEmpty())
initRecordCall.add( initRecordCall.add(
nn(nkCall, ident"toPreserve", id)) nn(nkCall, ident"toPreserve", id, ident"E"))
var procId = name var procId = name
procId[0] = procId[0].toLowerAscii procId[0] = procId[0].toLowerAscii
let cmt = nkCommentStmt.newNode let cmt = nkCommentStmt.newNode
cmt.comment = "Preserves constructor for ``" & name & "``." cmt.comment = "Preserves constructor for ``" & name & "``."
result.add nn(nkProcDef, result.add nn(nkProcDef,
exportIdent(procId), procId.ident.accQuote.toExport,
newEmpty(),
newEmpty(), newEmpty(),
nn(nkGenericParams, nn(nkIdentDefs, ident"E", newEmpty(), ident"void")),
params, params,
newEmpty(), newEmpty(),
newEmpty(), newEmpty(),
@ -486,21 +555,24 @@ proc generateProcs(result: var seq[PNode]; scm: Schema; name: string; sn: Schema
block: block:
let paramId = name.toLowerAscii.ident.accQuote let paramId = name.toLowerAscii.ident.accQuote
initRecordCall = nn(nkCall, initRecordCall = nn(nkCall,
ident"initRecord", nn(nkBracketExpr, ident"initRecord", ident"E"),
sn.nodes[0].toNimLit) nimLit(scm, sn.nodes[0]))
for i, field in sn.nodes: for i, field in sn.nodes:
if i > 0: if i > 0:
initRecordCall.add nn(nkCall, initRecordCall.add nn(nkCall,
ident"toPreserve", ident"toPreserve",
nn(nkDotExpr, paramId, field.ident)) nn(nkDotExpr, paramId, field.ident),
ident"E")
result.add nn(nkProcDef, result.add nn(nkProcDef,
exportIdent("toPreserveHook"), exportIdent("toPreserveHook"),
newEmpty(), newEmpty(),
newEmpty(), newEmpty(),
nn(nkFormalParams, nn(nkFormalParams,
ident"Preserve", preserveIdent(),
nn(nkIdentDefs, nn(nkIdentDefs,
paramId, ident(name), newEmpty())), paramId, ident(name), newEmpty()),
nn(nkIdentDefs,
ident"E", ident"typedesc", newEmpty())),
newEmpty(), newEmpty(),
newEmpty(), newEmpty(),
nn(nkStmtList, initRecordCall)) nn(nkStmtList, initRecordCall))
@ -511,9 +583,11 @@ proc generateProcs(result: var seq[PNode]; scm: Schema; name: string; sn: Schema
newEmpty(), newEmpty(),
newEmpty(), newEmpty(),
nn(nkFormalParams, nn(nkFormalParams,
ident"Preserve", preserveIdent(),
nn(nkIdentDefs, nn(nkIdentDefs,
paramId, ident(name), newEmpty())), paramId, ident(name), newEmpty()),
nn(nkIdentDefs,
ident"E", ident"typedesc", newEmpty())),
newEmpty(), newEmpty(),
newEmpty(), newEmpty(),
nn(nkStmtList, tupleConstructor(scm, sn, paramId))) nn(nkStmtList, tupleConstructor(scm, sn, paramId)))
@ -534,9 +608,6 @@ proc collectRefImports(imports: PNode; sn: SchemaNode) =
collectRefImports(imports, child) collectRefImports(imports, child)
proc collectRefImports(imports: PNode; scm: Schema) = proc collectRefImports(imports: PNode; scm: Schema) =
if scm.embeddedType.contains {'.'}:
let m = split(scm.embeddedType, '.', 1 )[0]
imports.add ident(m)
for _, def in scm.definitions: for _, def in scm.definitions:
collectRefImports(imports, def) collectRefImports(imports, def)
@ -548,35 +619,39 @@ proc generateNimFile*(scm: Schema; path: string) =
megaType: PNode megaType: PNode
for name, def in scm.definitions.pairs: for name, def in scm.definitions.pairs:
if isConst(scm, def): if isConst(scm, def):
generateConstProcs(procs, name, def) generateConstProcs(procs, scm, name, def)
else: else:
var name = name var name = name
name[0] = name[0].toUpperAscii name[0] = name[0].toUpperAscii
var defIdent = parameterize(ident(name), isEmbeddable(scm, def))
if megaType.isNil: if megaType.isNil:
megaType = ident(name) megaType = defIdent
else: else:
megaType = nn(nkInfix, megaType = nn(nkInfix,
ident"|", megaType, ident(name)) ident"|", megaType, defIdent)
let t = nimTypeOf(scm, knownTypes, def, name) let (t, embeddable) =
if def.kind == snkAny: (preserveIdent(), true)
else: nimTypeOf(scm, knownTypes, def, name)
t.comment = "``" & $def & "``" t.comment = "``" & $def & "``"
case def.kind case def.kind
of snkAtom: of snkAtom:
knownTypes[name] = nkTypeDef.newNode.add( knownTypes[name] = (nkTypeDef.newNode.add(
name.ident.toExport, newEmpty(), t) name.ident.toExport, embeddingParams(false), t),
embeddable)
else: else:
if def.kind == snkRecord: if def.kind == snkRecord:
knownTypes[name] = typeDef(def, name, t) knownTypes[name] = (typeDef(def, name, t, embeddable), embeddable)
else: else:
case t.kind case t.kind
of nkEnumTy: of nkEnumTy:
knownTypes[name] = nn(nkTypeDef, knownTypes[name] = (nn(nkTypeDef,
nn(nkPragmaExpr, nn(nkPragmaExpr,
name.ident.toExport, name.ident.toExport,
nn(nkPragma, ident"pure")), nn(nkPragma, ident"pure")),
newEmpty(), newEmpty(),
t) t), false)
of nkDistinctTy: of nkDistinctTy:
knownTypes[name] = nn(nkTypeDef, knownTypes[name] = (nn(nkTypeDef,
nn(nkPragmaExpr, nn(nkPragmaExpr,
name.ident.toExport, name.ident.toExport,
nn(nkPragma, nn(nkPragma,
@ -584,12 +659,13 @@ proc generateNimFile*(scm: Schema; path: string) =
ident"borrow", ident"borrow",
accQuote(ident".")))), accQuote(ident".")))),
newEmpty(), newEmpty(),
t) t), embeddable)
else: else:
knownTypes[name] = nn(nkTypeDef, knownTypes[name] = (nn(nkTypeDef,
name.ident.toExport, newEmpty(), t) name.ident.toExport, embeddingParams(embeddable), t),
embeddable)
generateProcs(procs, scm, name, def) generateProcs(procs, scm, name, def)
for typeDef in knownTypes.values: for (typeDef, _) in knownTypes.values:
typeSection.add typeDef typeSection.add typeDef
var imports = nkImportStmt.newNode.add( var imports = nkImportStmt.newNode.add(
ident"std/typetraits", ident"std/typetraits",
@ -598,7 +674,7 @@ proc generateNimFile*(scm: Schema; path: string) =
procs.add nn(nkProcDef, procs.add nn(nkProcDef,
"$".ident.accQuote.toExport, "$".ident.accQuote.toExport,
newEmpty(), newEmpty(),
newEmpty(), nn(nkGenericParams, nn(nkIdentDefs, ident"E", newEmpty(), newEmpty())),
nn(nkFormalParams, nn(nkFormalParams,
ident"string", ident"string",
nn(nkIdentDefs, nn(nkIdentDefs,
@ -608,11 +684,12 @@ proc generateNimFile*(scm: Schema; path: string) =
newEmpty(), newEmpty(),
newEmpty(), newEmpty(),
nn(nkStmtList, nn(nkStmtList,
nn(nkCall, ident"$", nn(nkCall, ident"toPreserve", ident"x")))) nn(nkCall, ident"$",
nn(nkCall, ident"toPreserve", ident"x", ident"E"))))
procs.add nn(nkProcDef, procs.add nn(nkProcDef,
"encode".ident.accQuote.toExport, "encode".ident.accQuote.toExport,
newEmpty(), newEmpty(),
newEmpty(), nn(nkGenericParams, nn(nkIdentDefs, ident"E", newEmpty(), newEmpty())),
nn(nkFormalParams, nn(nkFormalParams,
nn(nkBracketExpr, ident"seq", ident"byte"), nn(nkBracketExpr, ident"seq", ident"byte"),
nn(nkIdentDefs, nn(nkIdentDefs,
@ -622,7 +699,8 @@ proc generateNimFile*(scm: Schema; path: string) =
newEmpty(), newEmpty(),
newEmpty(), newEmpty(),
nn(nkStmtList, nn(nkStmtList,
nn(nkCall, ident"encode", nn(nkCall, ident"toPreserve", ident"x")))) nn(nkCall, ident"encode", nn(nkCall,
ident"toPreserve", ident"x", ident"E"))))
var module = newNode(nkStmtList).add( var module = newNode(nkStmtList).add(
imports, imports,
typeSection typeSection

View File

@ -1,64 +0,0 @@
# SPDX-FileCopyrightText: 2021 ☭ Emery Hemingway
# SPDX-License-Identifier: Unlicense
import std/[macros, typetraits]
import ../preserves
type RecordClass* = object
## Type of a preserves record.
label*: Preserve
arity*: Natural
proc `$`*(rec: RecordClass): string =
$rec.label & "/" & $rec.arity
proc isClassOf*(rec: RecordClass; val: Preserve): bool =
## Compare the label and arity of ``val`` to the record type ``rec``.
if val.kind == pkRecord:
assert(val.record.len > 0)
result = val.label == rec.label and rec.arity == val.arity
proc classOf*(val: Preserve): RecordClass =
## Derive the ``RecordClass`` of ``val``.
if val.kind != pkRecord:
raise newException(Defect, "cannot derive class of non-record value " & $val)
assert(val.record.len > 0)
RecordClass(label: val.label, arity: val.arity)
proc classOf*[T](x: T): RecordClass =
## Derive the ``RecordClass`` of ``x``.
when not T.hasCustomPragma(record): {.error: "no {.record.} pragma on " & $T.}
result.label = preserves.symbol(T.getCustomPragmaVal(record))
for k, v in x.fieldPairs: inc(result.arity)
proc classOf*(T: typedesc[tuple]): RecordClass =
## Derive the ``RecordClass`` of ``T``.
when not T.hasCustomPragma(record): {.error: "no {.record.} pragma on " & $T.}
RecordClass(
label: preserves.symbol(T.getCustomPragmaVal(record)),
arity: tupleLen(T))
proc init*(rec: RecordClass; fields: varargs[Preserve, toPreserve]): Preserve =
## Initialize a new record value.
assert(fields.len == rec.arity, $(fields.toPreserve) & " (arity " & $fields.len & ") is not of arity " & $rec.arity)
result = initRecord(rec.label, fields)
proc init*(T: typedesc[tuple]; fields: varargs[Preserve, toPreserve]): Preserve =
## Initialize a new record value.
init(classOf(T), fields)
proc `%`*(rec: RecordClass; fields: openArray[Preserve]): Preserve =
## Initialize a simple record value.
init(rec, fields)
proc `%`*(rec: RecordClass; field: Preserve): Preserve =
## Initialize a simple record value.
init(rec, [field])
proc `%`*[T](rec: RecordClass; field: T): Preserve =
## Initialize a simple record value.
init(rec, [toPreserve field])
proc `%`*(T: typedesc[tuple]; fields: varargs[Preserve, toPreserve]): Preserve =
## Initialize a new record value.
`%`(classOf(T), fields)

View File

@ -11,6 +11,8 @@ import npeg
import ../preserves, ./parse, ./pegs import ../preserves, ./parse, ./pegs
type type
Value = Preserve[void]
Stack = seq[tuple[node: SchemaNode, pos: int]] Stack = seq[tuple[node: SchemaNode, pos: int]]
SchemaNodeKind* = enum SchemaNodeKind* = enum
@ -52,7 +54,7 @@ type
of snkEmbedded: of snkEmbedded:
embed*: SchemaNode embed*: SchemaNode
of snkLiteral: of snkLiteral:
value*: Preserve value*: Value
of snkNamed: of snkNamed:
name*: string name*: string
pattern*: SchemaNode pattern*: SchemaNode
@ -271,7 +273,7 @@ const parser = peg("Schema", p: ParseState):
AltRecord <- '<' * >id * *(S * NamedPattern) * '>': AltRecord <- '<' * >id * *(S * NamedPattern) * '>':
let let
id = SchemaNode(kind: snkLiteral, value: symbol($1)) id = SchemaNode(kind: snkLiteral, value: symbol[void]($1))
n = SchemaNode(kind: snkAlt, n = SchemaNode(kind: snkAlt,
altLabel: $1, altLabel: $1,
altBranch: snkRecord.newSchemaNode.add(id).add(takeStackAt())) altBranch: snkRecord.newSchemaNode.add(id).add(takeStackAt()))

View File

@ -1,8 +1,8 @@
# SPDX-FileCopyrightText: 2021 ☭ Emery Hemingway # SPDX-FileCopyrightText: 2021 ☭ Emery Hemingway
# SPDX-License-Identifier: Unlicense # SPDX-License-Identifier: Unlicense
import std/[options, unittest] import std/[options, tables, unittest]
import bigints, preserves, preserves/records import bigints, preserves
suite "conversions": suite "conversions":
test "dictionary": test "dictionary":
@ -30,7 +30,15 @@ suite "conversions":
check(prs.kind == pkRecord) check(prs.kind == pkRecord)
check($prs == """<foo 1 2 <bar "ku">>""") check($prs == """<foo 1 2 <bar "ku">>""")
check(preserveTo(prs, Foobar) == some(tup)) check(preserveTo(prs, Foobar) == some(tup))
check(classOf(tup) == classOf(prs))
test "tables":
var a: Table[int, string]
for i, s in ["a", "b", "c"]: a[i] = s
let b = toPreserve(a)
check($b == """{0: "a" 1: "b" 2: "c"}""")
var c: Table[int, string]
check(fromPreserve(c, b))
check(a == c)
suite "%": suite "%":
template check(p: Preserve; s: string) = template check(p: Preserve; s: string) =