Dump variant objects with transient fields

* tuples not working yet
 * loading not woring yet
 * added tests
This commit is contained in:
Felix Krause 2016-10-15 17:58:29 +02:00
parent b3d83025f7
commit 3ab3dc7ad0
2 changed files with 174 additions and 33 deletions

View File

@ -38,6 +38,31 @@ type
of akDog: of akDog:
barkometer: int barkometer: int
DumbEnum = enum
deA, deB, deC, deD
NonVariantWithTransient = object
a, b, c, d: string
VariantWithTransient = object
gStorable, gTemporary: string
case kind: DumbEnum
of deA, deB:
cStorable, cTemporary: string
of deC:
alwaysThere: int
of deD:
neverThere: int
markAsTransient(NonVariantWithTransient, a)
markAsTransient(NonVariantWithTransient, c)
markAsTransient(VariantWithTransient, gTemporary)
markAsTransient(VariantWithTransient, cTemporary)
markAsTransient(VariantWithTransient, neverThere)
proc `$`(v: BetterInt): string {.borrow.} proc `$`(v: BetterInt): string {.borrow.}
proc `==`(left, right: BetterInt): bool {.borrow.} proc `==`(left, right: BetterInt): bool {.borrow.}
@ -451,6 +476,32 @@ suite "Serialization":
expectConstructionError(1, 32, "While constructing Animal: Missing field: \"purringIntensity\""): expectConstructionError(1, 32, "While constructing Animal: Missing field: \"purringIntensity\""):
load(input, result) load(input, result)
test "Dump non-variant object with transient fields":
let input = NonVariantWithTransient(a: "a", b: "b", c: "c", d: "d")
let output = dump(input, tsNone, asTidy, blockOnly)
assertStringEqual yamlDirs & "\nb: b\nd: d", output
test "Dump variant object with transient fields":
let input = @[VariantWithTransient(kind: deB, gStorable: "gs",
gTemporary: "gt", cStorable: "cs", cTemporary: "ct"),
VariantWithTransient(kind: deD, gStorable: "a", gTemporary: "b",
neverThere: 42)]
let output = dump(input, tsNone, asTidy, blockOnly)
assertStringEqual yamlDirs & """
-
-
gStorable: gs
-
kind: deB
-
cStorable: cs
-
-
gStorable: a
-
kind: deD""", output
test "Dump cyclic data structure": test "Dump cyclic data structure":
var var
a = newNode("a") a = newNode("a")

View File

@ -616,8 +616,10 @@ macro constructFieldValue(t: typedesc, stream: untyped, context: untyped,
newCall(bindSym("escape"), name)))))) newCall(bindSym("escape"), name))))))
proc isVariantObject(t: typedesc): bool {.compileTime.} = proc isVariantObject(t: typedesc): bool {.compileTime.} =
let tDesc = getType(t) var tDesc = getType(t)
if tDesc.kind != nnkObjectTy: return false if tDesc.kind == nnkBracketExpr: tDesc = getType(tDesc[1])
if tDesc.kind != nnkObjectTy:
return false
for child in tDesc[2].children: for child in tDesc[2].children:
if child.kind == nnkRecCase: return true if child.kind == nnkRecCase: return true
return false return false
@ -628,6 +630,8 @@ let
transientBitvectorProc {.compileTime.} = transientBitvectorProc {.compileTime.} =
newIdentNode(":transientObject") newIdentNode(":transientObject")
var transientVectors {.compileTime.} = newSeq[set[int16]]()
proc constructObject*[O: object|tuple]( proc constructObject*[O: object|tuple](
s: var YamlStream, c: ConstructionContext, result: var O) s: var YamlStream, c: ConstructionContext, result: var O)
{.raises: [YamlConstructionError, YamlStreamError].} = {.raises: [YamlConstructionError, YamlStreamError].} =
@ -685,30 +689,92 @@ proc constructObject*[O: object|tuple](
inc(i) inc(i)
else: ensureAllFieldsPresent(s, O, result, matched) else: ensureAllFieldsPresent(s, O, result, matched)
macro genRepresentObject(t: typedesc, value, childTagStyle: typed): typed = macro getTransientBitvector(t: typedesc): untyped =
echo "here" echo "getTransientBitvector"
result = quote do: result = quote do:
when compiles(`transientBitvectorProc`(`t`)): when compiles(`transientBitvectorProc`(`t`)):
const bitvector = `transientBitvectorProc`(`t`) transientVectors[`transientBitvectorProc`(`t`)]
var fieldIndex = 0'i16 else:
for name, value in fieldPairs(value): set[int16]({})
when compiles(`transientBitvectorProc`(`t`)): echo result.repr
if fieldIndex notin bitvector:
when isVariantObject(O): c.put(startMapEvent(yTagQuestionMark, yAnchorNone))
c.put(scalarEvent(name, if childTagStyle == tsNone: yTagQuestionMark else:
yTagNimField, yAnchorNone))
representChild(value, childTagStyle, c)
when isVariantObject(O): c.put(endMapEvent())
fieldIndex.inc()
else:
when isVariantObject(O): c.put(startMapEvent(yTagQuestionMark, yAnchorNone))
c.put(scalarEvent(name, if childTagStyle == tsNone: yTagQuestionMark else:
yTagNimField, yAnchorNone))
representChild(value, childTagStyle, c)
when isVariantObject(O): c.put(endMapEvent())
echo "did it"
proc representObject*[O: object|tuple](value: O, ts: TagStyle, macro genRepresentObject(t: typedesc, value, childTagStyle: typed): typed =
result = newStmtList()
let tSym = genSym(nskConst, ":tSym")
result.add(quote do:
when compiles(`transientBitvectorProc`(`t`)):
const `tSym` = `transientBitvectorProc`(`t`)
else:
const `tSym` = -1
)
let
tDecl = getType(t)
tDesc = getType(tDecl[1])
isVO = isVariantObject(t)
var fieldIndex = 0'i16
for child in tDesc[2].children:
if child.kind == nnkRecCase:
let
fieldName = $child[0]
fieldAccessor = newDotExpr(value, newIdentNode(fieldName))
result.add(quote do:
c.put(startMapEvent(yTagQuestionMark, yAnchorNone))
c.put(scalarEvent(`fieldName`, if `childTagStyle` == tsNone:
yTagQuestionMark else: yTagNimField, yAnchorNone))
representChild(`fieldAccessor`, `childTagStyle`, c)
c.put(endMapEvent())
)
let enumName = $getTypeInst(child[0])
var caseStmt = newNimNode(nnkCaseStmt).add(fieldAccessor)
for bIndex in 1 .. len(child) - 1:
var curBranch: NimNode
var recListIndex = 0
case child[bIndex].kind
of nnkOfBranch:
curBranch = newNimNode(nnkOfBranch)
while child[bIndex][recListIndex].kind == nnkIntLit:
curBranch.add(newCall(enumName, newLit(child[bIndex][recListIndex].intVal)))
inc(recListIndex)
of nnkElse:
curBranch = newNimNode(nnkElse)
else:
internalError("Unexpected child kind: " & $child[bIndex].kind)
doAssert child[bIndex][recListIndex].kind == nnkRecList
var curStmtList = newStmtList()
if child[bIndex][recListIndex].len > 0:
for item in child[bIndex][recListIndex].children:
inc(fieldIndex)
let
name = $item
itemAccessor = newDotExpr(value, newIdentNode(name))
curStmtList.add(quote do:
when `tSym` == -1 or `fieldIndex` notin transientVectors[`tSym`]:
c.put(startMapEvent(yTagQuestionMark, yAnchorNone))
c.put(scalarEvent(`name`, if `childTagStyle` == tsNone:
yTagQuestionMark else: yTagNimField, yAnchorNone))
representChild(`itemAccessor`, `childTagStyle`, c)
c.put(endMapEvent())
)
else:
curStmtList.add(newNimNode(nnkDiscardStmt).add(newEmptyNode()))
curBranch.add(curStmtList)
caseStmt.add(curBranch)
result.add(caseStmt)
else:
let
name = $child
childAccessor = newDotExpr(value, newIdentNode(name))
result.add(quote do:
when `tSym` == -1 or `fieldIndex` notin transientVectors[`tSym`]:
when `isVO`: c.put(startMapEvent(yTagQuestionMark, yAnchorNone))
c.put(scalarEvent(`name`, if `childTagStyle` == tsNone:
yTagQuestionMark else: yTagNimField, yAnchorNone))
representChild(`childAccessor`, `childTagStyle`, c)
when `isVO`: c.put(endMapEvent())
)
inc(fieldIndex)
proc representObject*[O: object](value: O, ts: TagStyle,
c: SerializationContext, tag: TagId) = c: SerializationContext, tag: TagId) =
## represents a Nim object or tuple as YAML mapping ## represents a Nim object or tuple as YAML mapping
let childTagStyle = if ts == tsRootOnly: tsNone else: ts let childTagStyle = if ts == tsRootOnly: tsNone else: ts
@ -717,7 +783,20 @@ proc representObject*[O: object|tuple](value: O, ts: TagStyle,
genRepresentObject(O, value, childTagStyle) genRepresentObject(O, value, childTagStyle)
when isVariantObject(O): c.put(endSeqEvent()) when isVariantObject(O): c.put(endSeqEvent())
else: c.put(endMapEvent()) else: c.put(endMapEvent())
static: echo "represented " & typetraits.name(O)
proc representObject*[O: tuple](value: O, ts: TagStyle,
c: SerializationContext, tag: TagId) =
let childTagStyle = if ts == tsRootOnly: tsNone else: ts
const bitvector = getTransientBitvector(O)
var fieldIndex = 0'i16
c.put(startMapEvent(tag, yAnchorNone))
for name, fvalue in fieldPairs(value):
#if fieldIndex notin bitvector: TODO: fix!
c.put(scalarEvent(name, if childTagStyle == tsNone:
yTagQuestionMark else: yTagNimField, yAnchorNone))
representChild(fvalue, childTagStyle, c)
inc(fieldIndex)
c.put(endMapEvent())
proc constructObject*[O: enum](s: var YamlStream, c: ConstructionContext, proc constructObject*[O: enum](s: var YamlStream, c: ConstructionContext,
result: var O) result: var O)
@ -963,10 +1042,8 @@ proc representChild*[O](value: O, ts: TagStyle,
inc(count) inc(count)
if count == 1: c.put(scalarEvent("~", yTagNull)) if count == 1: c.put(scalarEvent("~", yTagNull))
else: else:
static: echo "repchild " & typetraits.name(O)
representObject(value, ts, c, representObject(value, ts, c,
if ts == tsNone: yTagQuestionMark else: yamlTag(O)) if ts == tsNone: yTagQuestionMark else: yamlTag(O))
static: echo "/repchild " & typetraits.name(O)
proc construct*[T](s: var YamlStream, target: var T) proc construct*[T](s: var YamlStream, target: var T)
{.raises: [YamlStreamError].} = {.raises: [YamlStreamError].} =
@ -1119,7 +1196,16 @@ macro getFieldIndex(t: typedesc, field: untyped): untyped =
for child in tDesc[2].children: for child in tDesc[2].children:
if child.kind == nnkRecCase: if child.kind == nnkRecCase:
for bIndex in 1 .. len(child) - 1: for bIndex in 1 .. len(child) - 1:
for item in child[bIndex][1].children: var bChildIndex = 0
case child[bIndex].kind
of nnkOfBranch:
while child[bIndex][bChildIndex].kind == nnkIntLit: inc(bChildIndex)
of nnkElse: discard
else:
internalError("Unexpected child kind: " &
$child[bIndex][bChildIndex].kind)
yAssert child[bIndex][bChildIndex].kind == nnkRecList
for item in child[bIndex][bChildIndex].children:
inc(fieldIndex) inc(fieldIndex)
yAssert item.kind == nnkSym yAssert item.kind == nnkSym
if $item == fieldName: if $item == fieldName:
@ -1137,15 +1223,19 @@ macro getFieldIndex(t: typedesc, field: untyped): untyped =
else: else:
result = newNimNode(nnkInt16Lit) result = newNimNode(nnkInt16Lit)
result.intVal = fieldIndex result.intVal = fieldIndex
echo "found fieldIndex of \"", fieldName, "\": ", result.intVal
macro markAsTransient*(t: typedesc, field: untyped): typed = macro markAsTransient*(t: typedesc, field: untyped): typed =
let mySet = genSym(nskVar, ident = ":mySym") let nextBitvectorIndex = transientVectors.len
quote do: result = quote do:
when compiles(`implicitVariantObjectMarker`(`t`)): when compiles(`implicitVariantObjectMarker`(`t`)):
{.fatal: "Cannot mark fields of implicit variant objects as transient." .} {.fatal: "Cannot mark fields of implicit variant objects as transient." .}
when not compiles(`transientBitvectorProc`(`t`)): when not compiles(`transientBitvectorProc`(`t`)):
var `mySet` {.compileTime.}: set[int16] = {} proc `transientBitvectorProc`*(myType: typedesc[`t`]): int
proc `transientBitvectorProc`*(myType: typedesc[`t`]): var set[int16]
{.compileTime.} = {.compileTime.} =
return `mySet` `nextBitvectorIndex`
static: `transientBitvectorProc`(`t`).incl(getFieldIndex(`t`, `field`)) static: transientVectors.add({})
static:
echo "setting transientBitvector at ", `transientBitvectorProc`(`t`)
transientVectors[`transientBitvectorProc`(`t`)].incl(getFieldIndex(`t`, `field`))
echo "transient bitvector is now ", transientVectors[`transientBitvectorProc`(`t`)]