From 376c0e04c586b3cafecae85576da034114ce721f Mon Sep 17 00:00:00 2001 From: Emery Hemingway Date: Mon, 28 Jun 2021 12:30:16 +0200 Subject: [PATCH] Add a {.record: "label".} pragma A pragma for serializing tuples and objects to or from records. --- src/preserves.nim | 72 ++++++++++++++++++++++++++++++++--------------- 1 file changed, 50 insertions(+), 22 deletions(-) diff --git a/src/preserves.nim b/src/preserves.nim index 12ae622..d4573b5 100644 --- a/src/preserves.nim +++ b/src/preserves.nim @@ -1,7 +1,8 @@ # SPDX-License-Identifier: ISC -import base64, endians, json, hashes, sets, streams, tables import bigints +import std/[base64, endians, json, hashes, macros, sets, streams, tables] + type PreserveKind* = enum @@ -228,14 +229,6 @@ func isRecord*(prs: Preserve): bool = 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) - -proc symbol*(s: string): Preserve {.inline.} = - ## Symbol constructor. - Preserve(kind: pkSymbol, symbol: s) - proc label*(prs: Preserve): Preserve {.inline.} = ## Return the label of a record value. prs.record[prs.record.high] @@ -252,6 +245,13 @@ iterator fields*(prs: Preserve): Preserve = ## Iterate the fields of a record value. for i in 0.. + ## ``` + proc toPreserve*[T](x: T): Preserve = ## Serializes `x` to Preserves; uses `toPreserveHook(x: A)` if it's in scope to ## customize serialization. @@ -473,8 +483,13 @@ proc toPreserve*[T](x: T): Preserve = 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) + when T.hasCustomPragma(record): + result = Preserve(kind: pkRecord) + for _, f in x.fieldPairs: result.record.add(toPreserve(f)) + result.record.add(symbol(T.getCustomPragmaVal(record))) + else: + 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: @@ -557,6 +572,14 @@ proc toJsonHook*(prs: Preserve): JsonNode = of pkEmbedded: raise newException(ValueError, "cannot convert embedded value to JSON") +proc checkRecordLabel(T: typedesc; prs: Preserve) = + when T.hasCustomPragma(record): + const label = symbol(T.getCustomPragmaVal(record)) + if prs.label != label: + raise newException(ValueError, $prs & " is not a record of class " & label.string) + else: + raise newException(Defect, $T & " lacks a {.record: \"…\".} annotation") + proc fromPreserve*[T](result: var T; prs: Preserve) = # Inplace version of `preserveTo`. when compiles(fromPreserveHook(result, prs)): @@ -577,21 +600,26 @@ proc fromPreserve*[T](result: var T; prs: Preserve) = 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 object | tuple: + case prs.kind + of pkRecord: + checkRecordLabel(T, prs) + var i: int + for k, v in result.fieldPairs: + fromPreserve(v, prs.record[i]) + inc(i) + of pkDictionary: + for k, v in result.fieldPairs: + fromPreserve(v, prs.dict[symbol(k)]) + else: + raise newException(ValueError, "cannot convert to Preserves value to type " & $T) + elif T is Ordinal | SomeInteger: + result = (T)prs.int 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..