Custom serialization for object fields (initial version)

This commit is contained in:
Zahary Karadjov 2019-07-31 01:52:17 +03:00
parent e126d48e6c
commit 99daa96284
No known key found for this signature in database
GPG Key ID: C8936F8A3073D609
1 changed files with 95 additions and 7 deletions

View File

@ -1,6 +1,9 @@
import
stew/shims/macros, stew/objects
type
FieldTag[RecordType; fieldName: static string; FieldType] = distinct void
template dontSerialize* {.pragma.}
## Specifies that a certain field should be ignored for
## the purposes of serialization
@ -48,10 +51,11 @@ macro enumAllSerializedFields*(T: type,
## a different order. Fields marked with the `dontSerialize` pragma
## are skipped.
##
var Timpl = getImpl(getType(T)[1])
var typeAst = getType(T)[1]
var typeImpl = getImpl(typeAst)
result = newStmtList()
for field in recordFields(Timpl):
for field in recordFields(typeImpl):
if field.readPragma("dontSerialize") != nil:
continue
@ -73,7 +77,7 @@ type
FieldReader*[RecordType, Reader] = tuple[
fieldName: string,
reader: proc (rec: var RecordType, reader: var Reader) {.nimcall.}
reader: proc (rec: var RecordType, reader: var Reader) {.gcsafe, nimcall.}
]
FieldReadersTable*[RecordType, Reader] = openarray[FieldReader[RecordType, Reader]]
@ -88,14 +92,20 @@ template totalSerializedFields*(T: type): int =
macro customSerialization*(field: untyped, definition): untyped =
discard
template readFieldIMPL[Reader](field: type FieldTag,
reader: var Reader): untyped =
mixin readValue
reader.readValue(field.FieldType)
proc makeFieldReadersTable(RecordType, Reader: distinct type):
seq[FieldReader[RecordType, Reader]] =
mixin enumAllSerializedFields
mixin enumAllSerializedFields, readFieldIMPL
enumAllSerializedFields(RecordType, fieldName, FieldType):
proc readField(obj: var RecordType, reader: var Reader) {.nimcall.} =
try:
obj.field(fieldName) = reader.readValue(FieldType)
type F = FieldTag[RecordType, fieldName, type(FieldType)]
obj.field(fieldName) = readFieldIMPL(F, reader)
except SerializationError:
raise
except CatchableError as err:
@ -105,7 +115,7 @@ proc makeFieldReadersTable(RecordType, Reader: distinct type):
result.add((fieldName, readField))
proc fieldReadersTable*(RecordType, Reader: distinct type):
ptr seq[FieldReader[RecordType, Reader]] {.gcsafe.} =
ptr seq[FieldReader[RecordType, Reader]] =
mixin readValue
var tbl {.global.} = makeFieldReadersTable(RecordType, Reader)
{.gcsafe.}:
@ -175,3 +185,81 @@ macro setSerializedFields*(T: typedesc, fields: varargs[untyped]): untyped =
return getAst(payload(T, fieldsArray))
proc getReaderAndWriter(customSerializationBody: NimNode): (NimNode, NimNode) =
template fail(n) =
error "useCustomSerialization expects a block with only `read` and `write` definitions", n
for n in customSerializationBody:
if n.kind in nnkCallKinds:
if eqIdent(n[0], "read"):
result[0] = n[1]
elif eqIdent(n[0], "write"):
result[1] = n[1]
else:
fail n[0]
elif n.kind == nnkCommentStmt:
continue
else:
fail n
let
# Identifiers affecting the public interface of the library:
readerVar {.compileTime.} = ident "reader"
writerVar {.compileTime.} = ident "writer"
holderVar {.compileTime.} = ident "holder"
valueVar {.compileTime.} = ident "value"
proc genCustomSerializationForField(Format, field,
readBody, writeBody: NimNode): NimNode =
var
RecordType = field[0]
fieldIdent = field[1]
fieldName = newLit $fieldIdent
FieldType = genSym(nskType, "FieldType")
result = newStmtList()
result.add quote do:
type `FieldType` = type default(`RecordType`).`fieldIdent`
if readBody != nil:
result.add quote do:
type Reader = ReaderType(`Format`)
proc readFieldIMPL*(F: type FieldTag[`RecordType`, `fieldName`, auto],
`readerVar`: var Reader): `FieldType` =
`readBody`
if writeBody != nil:
result.add quote do:
type Writer = WriterType(`Format`)
proc writeFieldIMPL*(F: type FieldTag[`RecordType`, `fieldName`, auto],
`writerVar`: var Writer) =
`writeBody`
proc genCustomSerializationForType(Format, typ: NimNode,
readBody, writeBody: NimNode): NimNode =
result = newStmtList()
if readBody != nil:
result.add quote do:
type Reader = ReaderType(`Format`)
proc readValue*(`readerVar`: var Reader, T: type `typ`): `typ` =
`readBody`
if writeBody != nil:
result.add quote do:
type Writer = WriterType(`Format`)
proc writeValue*(`writerVar`: var Writer, `valueVar`: `typ`) =
`writeBody`
macro useCustomSerialization*(Format: typed, field: untyped, body: untyped): untyped =
let (readBody, writeBody) = getReaderAndWriter(body)
if field.kind == nnkDotExpr:
result = genCustomSerializationForField(Format, field, readBody, writeBody)
elif field.kind in {nnkIdent, nnkAccQuoted}:
result = genCustomSerializationForType(Format, field, readBody, writeBody)
else:
error "useCustomSerialization expects a type name or a field of a type (e.g. MyType.myField)"
when defined(debugUseCustomSerialization):
echo result.repr