mirror of https://github.com/status-im/NimYAML.git
replaced field/object macros with annotations; fixes #66
This commit is contained in:
parent
96254f1900
commit
bce3981849
|
@ -6,7 +6,11 @@ type
|
|||
ContainerKind = enum
|
||||
ckString, ckInt, ckBool, ckPerson, ckNone
|
||||
|
||||
Container = object
|
||||
# {.implicit.} tells NimYAML to use Container
|
||||
# as implicit type.
|
||||
# only possible with variant object types where
|
||||
# each branch contains at most one object.
|
||||
Container {.implicit.} = object
|
||||
case kind: ContainerKind
|
||||
of ckString:
|
||||
strVal: string
|
||||
|
@ -21,11 +25,6 @@ type
|
|||
|
||||
setTagUri(Person, nimTag("demo:Person"))
|
||||
|
||||
# tell NimYAML to use Container as implicit type.
|
||||
# only possible with variant object types where
|
||||
# each branch contains at most one object.
|
||||
markAsImplicit(Container)
|
||||
|
||||
var list: seq[Container]
|
||||
|
||||
var s = newFileStream("in.yaml")
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
import "../yaml"
|
||||
import unittest
|
||||
|
||||
type
|
||||
Config = ref object
|
||||
docs_root* {.defaultVal: "~/.example".}: string
|
||||
drafts_root*: string
|
||||
|
||||
Stuff = ref object
|
||||
a {.transient.}: string
|
||||
b: string
|
||||
|
||||
Part {.ignore: ["a", "b"].} = ref object
|
||||
c: string
|
||||
|
||||
IgnoreAnything {.ignore: [].} = ref object
|
||||
warbl: int
|
||||
|
||||
ContainerKind = enum
|
||||
ckString, ckInt
|
||||
|
||||
Container {.implicit.} = object
|
||||
case kind: ContainerKind
|
||||
of ckString:
|
||||
strVal: string
|
||||
of ckInt:
|
||||
intVal: int
|
||||
|
||||
suite "Serialization Annotations":
|
||||
test "load default value":
|
||||
let input = "drafts_root: foo"
|
||||
var result: Config
|
||||
load(input, result)
|
||||
assert result.docs_root == "~/.example", "docs_root is " & result.docs_root
|
||||
assert result.drafts_root == "foo", "drafts_root is " & result.drafts_root
|
||||
|
||||
test "load into object with transient fields":
|
||||
let input = "b: warbl"
|
||||
var result: Stuff
|
||||
load(input, result)
|
||||
assert result.b == "warbl"
|
||||
assert result.a == ""
|
||||
|
||||
test "load into object with ignored keys":
|
||||
let input = "{a: foo, c: bar, b: baz}"
|
||||
var result: Part
|
||||
load(input, result)
|
||||
assert result.c == "bar"
|
||||
|
||||
test "load into object ignoring all other keys":
|
||||
let input = "{tuirae: fg, rtuco: fgh, warbl: 1}"
|
||||
var result: IgnoreAnything
|
||||
load(input, result)
|
||||
assert result.warbl == 1
|
||||
|
||||
test "load implicit variant object":
|
||||
let input = "[foo, 13]"
|
||||
var result: seq[Container]
|
||||
load(input, result)
|
||||
assert len(result) == 2
|
||||
assert result[0].kind == ckString
|
||||
assert result[1].kind == ckInt
|
|
@ -4,4 +4,4 @@
|
|||
# See the file "copying.txt", included in this
|
||||
# distribution, for details about the copyright.
|
||||
|
||||
import tlex, tjson, tserialization, tdom, tparser, tquickstart
|
||||
import tlex, tjson, tserialization, tdom, tparser, tquickstart, tannotations
|
|
@ -38,39 +38,29 @@ type
|
|||
of akDog: barkometer: int
|
||||
|
||||
DumbEnum = enum
|
||||
deA, deB, deC, deD
|
||||
deA, deB, deC
|
||||
|
||||
NonVariantWithTransient = object
|
||||
a, b, c, d: string
|
||||
a {.transient.}, b, c {.transient.}, d: string
|
||||
|
||||
VariantWithTransient = object
|
||||
gStorable, gTemporary: string
|
||||
gStorable: string
|
||||
gTemporary {.transient.}: string
|
||||
case kind: DumbEnum
|
||||
of deA, deB:
|
||||
cStorable, cTemporary: string
|
||||
of deC:
|
||||
of deA:
|
||||
cStorable: string
|
||||
cTemporary {.transient.}: string
|
||||
of deB:
|
||||
alwaysThere: int
|
||||
of deD:
|
||||
neverThere: int
|
||||
of deC:
|
||||
neverThere {.transient.}: int
|
||||
|
||||
WithDefault = object
|
||||
a, b, c, d: string
|
||||
a, b {.defaultVal: "b".}, c, d {.defaultVal: "d".}: string
|
||||
|
||||
WithIgnoredField = object
|
||||
WithIgnoredField {.ignore: ["z"].} = object
|
||||
x, y: int
|
||||
|
||||
markAsTransient(NonVariantWithTransient, a)
|
||||
markAsTransient(NonVariantWithTransient, c)
|
||||
|
||||
markAsTransient(VariantWithTransient, gTemporary)
|
||||
markAsTransient(VariantWithTransient, cTemporary)
|
||||
markAsTransient(VariantWithTransient, neverThere)
|
||||
|
||||
setDefaultValue(WithDefault, b, "b")
|
||||
setDefaultValue(WithDefault, d, "d")
|
||||
|
||||
ignoreInputKey(WithIgnoredField, "z")
|
||||
|
||||
proc `$`(v: BetterInt): string {.borrow.}
|
||||
proc `==`(left, right: BetterInt): bool {.borrow.}
|
||||
|
||||
|
@ -531,26 +521,26 @@ suite "Serialization":
|
|||
assertStringEqual yamlDirs & "\nb: b\nd: d", output
|
||||
|
||||
test "Load variant object with transient fields":
|
||||
let input = "[[gStorable: gs, kind: deB, cStorable: cs], [gStorable: a, kind: deD]]"
|
||||
let input = "[[gStorable: gs, kind: deA, cStorable: cs], [gStorable: a, kind: deC]]"
|
||||
var result: seq[VariantWithTransient]
|
||||
load(input, result)
|
||||
assert result.len == 2
|
||||
assert result[0].kind == deB
|
||||
assert result[0].kind == deA
|
||||
assert result[0].gStorable == "gs"
|
||||
assert result[0].cStorable == "cs"
|
||||
assert result[1].kind == deD
|
||||
assert result[1].kind == deC
|
||||
assert result[1].gStorable == "a"
|
||||
|
||||
test "Load variant object with transient fields":
|
||||
let input = "[gStorable: gc, kind: deD, neverThere: foo]"
|
||||
test "Load variant object with transient fields, error":
|
||||
let input = "[gStorable: gc, kind: deC, neverThere: foo]"
|
||||
var result: VariantWithTransient
|
||||
expectConstructionError(1, 38, "While constructing VariantWithTransient: Field \"neverThere\" is transient and may not occur in input"):
|
||||
load(input, result)
|
||||
|
||||
test "Dump variant object with transient fields":
|
||||
let input = @[VariantWithTransient(kind: deB, gStorable: "gs",
|
||||
let input = @[VariantWithTransient(kind: deA, gStorable: "gs",
|
||||
gTemporary: "gt", cStorable: "cs", cTemporary: "ct"),
|
||||
VariantWithTransient(kind: deD, gStorable: "a", gTemporary: "b",
|
||||
VariantWithTransient(kind: deC, gStorable: "a", gTemporary: "b",
|
||||
neverThere: 42)]
|
||||
let output = dump(input, tsNone, asTidy, blockOnly)
|
||||
assertStringEqual yamlDirs & """
|
||||
|
@ -559,14 +549,14 @@ suite "Serialization":
|
|||
-
|
||||
gStorable: gs
|
||||
-
|
||||
kind: deB
|
||||
kind: deA
|
||||
-
|
||||
cStorable: cs
|
||||
-
|
||||
-
|
||||
gStorable: a
|
||||
-
|
||||
kind: deD""", output
|
||||
kind: deC""", output
|
||||
|
||||
test "Load object with ignored key":
|
||||
let input = "[{x: 1, y: 2}, {x: 3, z: 4, y: 5}, {z: [1, 2, 3], x: 4, y: 5}]"
|
||||
|
|
4
yaml.nim
4
yaml.nim
|
@ -38,8 +38,8 @@
|
|||
## * The hints API in `hints <yaml.hints.html>`_ provides a simple proc for
|
||||
## guessing the type of a scalar value.
|
||||
|
||||
import yaml / [dom, hints, parser, presenter,
|
||||
import yaml / [dom, hints, parser, presenter, annotations,
|
||||
serialization, stream, taglib, tojson]
|
||||
|
||||
export dom, hints, parser, presenter,
|
||||
export dom, hints, parser, presenter, annotations,
|
||||
serialization, stream, taglib, tojson
|
||||
|
|
|
@ -0,0 +1,69 @@
|
|||
# NimYAML - YAML implementation in Nim
|
||||
# (c) Copyright 2016-2020 Felix Krause
|
||||
#
|
||||
# See the file "copying.txt", included in this
|
||||
# distribution, for details about the copyright.
|
||||
|
||||
## =======================
|
||||
## Module yaml.annotations
|
||||
## =======================
|
||||
##
|
||||
## This module provides annotations for object fields that customize
|
||||
## (de)serialization behavior of those fields.
|
||||
|
||||
template defaultVal*(value : typed) {.pragma.}
|
||||
## This annotation can be assigned to an object field. During deserialization,
|
||||
## if no value for this field is given, the `value` parameter of this
|
||||
## annotation is used as value.
|
||||
##
|
||||
## Example usage:
|
||||
##
|
||||
## .. code-block::
|
||||
## type MyObject = object
|
||||
## a {.defaultVal: "foo".}: string
|
||||
## c {.defaultVal: (1,2).}: tuple[x, y: int]
|
||||
|
||||
template transient*() {.pragma.}
|
||||
## This annotation can be assigned to an object field. Any object field
|
||||
## carrying this annotation will not be serialized to YAML and cannot be given
|
||||
## a value when deserializing. Giving a value for this field during
|
||||
## deserialization is an error.
|
||||
##
|
||||
## Example usage:
|
||||
##
|
||||
## .. code-block::
|
||||
## type MyObject = object
|
||||
## a, b: string
|
||||
## c: int
|
||||
## markAsTransient(MyObject, a)
|
||||
## markAsTransient(MyObject, c)
|
||||
|
||||
template ignore*(keys : openarray[string]) {.pragma.}
|
||||
## This annotation can be assigned to an object type. All keys with the given
|
||||
## names in the input YAML mapping will be ignored when deserializing a value
|
||||
## of this type. This can be used to ignore parts of the YAML structure.
|
||||
##
|
||||
## Example usage:
|
||||
##
|
||||
## .. code-block::
|
||||
## type MyObject {.ignore("c").} = object
|
||||
## a, b: string
|
||||
|
||||
template implicit*() {.pragma.}
|
||||
## This annotation declares a variant object type as implicit.
|
||||
## This requires the type to consist of nothing but a case expression and each
|
||||
## branch of the case expression containing exactly one field - with the
|
||||
## exception that one branch may contain zero fields.
|
||||
##
|
||||
## Example usage:
|
||||
##
|
||||
## .. code-block::
|
||||
## ContainerKind = enum
|
||||
## ckString, ckInt
|
||||
##
|
||||
## type MyObject {.implicit.} = object
|
||||
## case kind: ContainerKind
|
||||
## of ckString:
|
||||
## strVal: string
|
||||
## of ckInt:
|
||||
## intVal: int
|
|
@ -17,8 +17,8 @@
|
|||
## information.
|
||||
|
||||
import tables, typetraits, strutils, macros, streams, times, parseutils, options
|
||||
import parser, taglib, presenter, stream, private/internal, hints
|
||||
export stream
|
||||
import parser, taglib, presenter, stream, private/internal, hints, annotations
|
||||
export stream, macros, annotations
|
||||
# *something* in here needs externally visible `==`(x,y: AnchorId),
|
||||
# but I cannot figure out what. binding it would be the better option.
|
||||
|
||||
|
@ -615,12 +615,13 @@ proc fieldCount(t: NimNode): int {.compiletime.} =
|
|||
var increment = 0
|
||||
case child[bIndex].kind
|
||||
of nnkOfBranch:
|
||||
let content = child[bIndex][len(child[bIndex])-1]
|
||||
# We cannot assume that child[bIndex][1] is a RecList due to
|
||||
# a one-liner like 'of akDog: barkometer' not resulting in a
|
||||
# RecList but in an Ident node.
|
||||
case child[bIndex][1].kind
|
||||
case content.kind
|
||||
of nnkRecList:
|
||||
increment = len(child[bIndex][1])
|
||||
increment = len(content)
|
||||
else:
|
||||
increment = 1
|
||||
of nnkElse:
|
||||
|
@ -648,75 +649,50 @@ proc checkDuplicate(s: NimNode, tName: string, name: string, i: int,
|
|||
newLit("While constructing " & tName & ": Duplicate field: " &
|
||||
escape(name))))))
|
||||
|
||||
let
|
||||
implicitVariantObjectMarker {.compileTime.} =
|
||||
newIdentNode(":implicitVariantObject")
|
||||
transientBitvectorProc {.compileTime.} =
|
||||
newIdentNode(":transientObject")
|
||||
defaultBitvectorProc {.compileTime.} =
|
||||
newIdentNode(":defaultBitvector")
|
||||
defaultValueGetter {.compileTime.} =
|
||||
newIdentNode(":defaultValueGetter")
|
||||
ignoredKeyListProc {.compileTime.} =
|
||||
newIdentNode(":ignoredKeyList")
|
||||
ignoreUnknownKeysProc {.compileTime.} =
|
||||
newIdentNode(":ignoreUnknownKeys")
|
||||
|
||||
var
|
||||
transientVectors {.compileTime.} = newSeq[set[int16]]()
|
||||
defaultVectors {.compileTime.} = newSeq[set[int16]]()
|
||||
ignoredKeyLists {.compileTime.} = newSeq[seq[string]]()
|
||||
|
||||
proc addDefaultOr(tName: string, i: int, o: NimNode,
|
||||
field, elseBranch, defaultValues: NimNode): NimNode {.compileTime.} =
|
||||
let
|
||||
dbp = defaultBitvectorProc
|
||||
t = newIdentNode(tName)
|
||||
field, elseBranch: NimNode): NimNode {.compileTime.} =
|
||||
result = quote do:
|
||||
when compiles(`dbp`(`t`)):
|
||||
when `i` in defaultVectors[`dbp`(`t`)]:
|
||||
`o`.`field` = `defaultValues`.`field`
|
||||
else: `elseBranch`
|
||||
when `o`.`field`.hasCustomPragma(defaultVal):
|
||||
`o`.`field` = `o`.`field`.getCustomPragmaVal(defaultVal)
|
||||
else: `elseBranch`
|
||||
|
||||
proc checkMissing(s: NimNode, t: NimNode, tName: string, field: NimNode,
|
||||
i: int, matched, o, defaultValues: NimNode):
|
||||
i: int, matched, o: NimNode):
|
||||
NimNode {.compileTime.} =
|
||||
result = newIfStmt((newCall("not", newNimNode(nnkBracketExpr).add(matched,
|
||||
newLit(i))), addDefaultOr(tName, i, o, field,
|
||||
newNimNode(nnkRaiseStmt).add(newCall(
|
||||
bindSym("constructionError"), s, newLit("While constructing " &
|
||||
tName & ": Missing field: " & escape($field)))), defaultValues)))
|
||||
let fName = escape($field)
|
||||
result = quote do:
|
||||
when not `o`.`field`.hasCustomPragma(transient):
|
||||
if not `matched`[`i`]:
|
||||
when `o`.`field`.hasCustomPragma(defaultVal):
|
||||
`o`.`field` = `o`.`field`.getCustomPragmaVal(defaultVal)
|
||||
else:
|
||||
raise constructionError(`s`, "While constructing " & `tName` &
|
||||
": Missing field: " & `fName`)
|
||||
|
||||
proc markAsFound(i: int, matched: NimNode): NimNode {.compileTime.} =
|
||||
newAssignment(newNimNode(nnkBracketExpr).add(matched, newLit(i)),
|
||||
newLit(true))
|
||||
|
||||
proc ifNotTransient(tSym: NimNode, fieldIndex: int, content: openarray[NimNode],
|
||||
elseError: bool = false, s: NimNode = nil, tName, fName: string = ""):
|
||||
proc ifNotTransient(o, field: NimNode,
|
||||
content: openarray[NimNode],
|
||||
elseError: bool, s: NimNode, tName, fName: string = ""):
|
||||
NimNode {.compileTime.} =
|
||||
var stmts = newStmtList(content)
|
||||
result = quote do:
|
||||
when `tSym` == -1 or `fieldIndex` notin transientVectors[`tSym`]:
|
||||
`stmts`
|
||||
if result.kind == nnkStmtList and result.len == 1: result = result[0]
|
||||
result = newStmtList(result)
|
||||
if elseError:
|
||||
result[0].add(newNimNode(nnkElse).add(quote do:
|
||||
result = quote do:
|
||||
when `o`.`field`.hasCustomPragma(transient):
|
||||
raise constructionError(`s`, "While constructing " & `tName` &
|
||||
": Field \"" & `fName` & "\" is transient and may not occur in input")
|
||||
))
|
||||
|
||||
macro ensureAllFieldsPresent(s: YamlStream, t: typedesc, tIndex: int, o: typed,
|
||||
matched: typed) =
|
||||
let
|
||||
dbp = defaultBitvectorProc
|
||||
defaultValues = genSym(nskConst, "defaultValues")
|
||||
else:
|
||||
`stmts`
|
||||
else:
|
||||
result = quote do:
|
||||
when compiles(`dbp`(`t`)):
|
||||
const `defaultValues` = `defaultValueGetter`(`t`)
|
||||
if result.kind == nnkStmtList and result.len == 1: result = result[0]
|
||||
result = newStmtList(result)
|
||||
when not `o`.`field`.hasCustomPragma(transient):
|
||||
`stmts`
|
||||
|
||||
macro ensureAllFieldsPresent(s: YamlStream, t: typedesc, o: typed,
|
||||
matched: typed) =
|
||||
result = newStmtList()
|
||||
let
|
||||
tDecl = getType(t)
|
||||
tName = $tDecl[1]
|
||||
|
@ -724,8 +700,7 @@ macro ensureAllFieldsPresent(s: YamlStream, t: typedesc, tIndex: int, o: typed,
|
|||
var field = 0
|
||||
for child in tDesc[2].children:
|
||||
if child.kind == nnkRecCase:
|
||||
result.add(checkMissing(s, t, tName, child[0], field, matched, o,
|
||||
defaultValues))
|
||||
result.add(checkMissing(s, t, tName, child[0], field, matched, o))
|
||||
for bIndex in 1 .. len(child) - 1:
|
||||
let discChecks = newStmtList()
|
||||
var
|
||||
|
@ -741,24 +716,14 @@ macro ensureAllFieldsPresent(s: YamlStream, t: typedesc, tIndex: int, o: typed,
|
|||
else: internalError("Unexpected child kind: " & $child[bIndex].kind)
|
||||
for item in child[bIndex][recListIndex].recListItems:
|
||||
inc(field)
|
||||
discChecks.add(checkMissing(s, t, tName, item, field, matched, o,
|
||||
defaultValues))
|
||||
result.add(ifNotTransient(tIndex, field,
|
||||
[newIfStmt((infix(newDotExpr(o, newIdentNode($child[0])),
|
||||
"in", curValues), discChecks))]))
|
||||
discChecks.add(checkMissing(s, t, tName, item, field, matched, o))
|
||||
result.add(newIfStmt((infix(newDotExpr(o, newIdentNode($child[0])),
|
||||
"in", curValues), discChecks)))
|
||||
else:
|
||||
result.add(ifNotTransient(tIndex, field,
|
||||
[checkMissing(s, t, tName, child, field, matched, o, defaultValues)]))
|
||||
result.add(checkMissing(s, t, tName, child, field, matched, o))
|
||||
inc(field)
|
||||
|
||||
macro fetchTransientIndex(t: typedesc, tIndex: untyped) =
|
||||
quote do:
|
||||
when compiles(`transientBitvectorProc`(`t`)):
|
||||
const `tIndex` = `transientBitvectorProc`(`t`)
|
||||
else:
|
||||
const `tIndex` = -1
|
||||
|
||||
macro constructFieldValue(t: typedesc, tIndex: int, stream: untyped,
|
||||
macro constructFieldValue(t: typedesc, stream: untyped,
|
||||
context: untyped, name: untyped, o: untyped,
|
||||
matched: untyped, failOnUnknown: bool) =
|
||||
let
|
||||
|
@ -823,7 +788,7 @@ macro constructFieldValue(t: typedesc, tIndex: int, stream: untyped,
|
|||
newCall(bindSym("constructionError"), stream,
|
||||
infix(newStrLitNode("Field " & $item & " not allowed for " &
|
||||
$child[0] & " == "), "&", prefix(discriminant, "$"))))))
|
||||
ob.add(ifNotTransient(tIndex, fieldIndex,
|
||||
ob.add(ifNotTransient(o, item,
|
||||
[checkDuplicate(stream, tName, $item, fieldIndex, matched),
|
||||
ifStmt, markAsFound(fieldIndex, matched)], true, stream, tName,
|
||||
$item))
|
||||
|
@ -832,7 +797,7 @@ macro constructFieldValue(t: typedesc, tIndex: int, stream: untyped,
|
|||
yAssert child.kind == nnkSym
|
||||
var ob = newNimNode(nnkOfBranch).add(newStrLitNode($child))
|
||||
let field = newDotExpr(o, newIdentNode($child))
|
||||
ob.add(ifNotTransient(tIndex, fieldIndex,
|
||||
ob.add(ifNotTransient(o, child,
|
||||
[checkDuplicate(stream, tName, $child, fieldIndex, matched),
|
||||
newCall("constructChild", stream, context, field),
|
||||
markAsFound(fieldIndex, matched)], true, stream, tName, $child))
|
||||
|
@ -855,19 +820,11 @@ proc isVariantObject(t: NimNode): bool {.compileTime.} =
|
|||
if child.kind == nnkRecCase: return true
|
||||
return false
|
||||
|
||||
macro injectIgnoredKeyList(t: typedesc, ident: untyped) =
|
||||
result = quote do:
|
||||
when compiles(`ignoredKeyListProc`(`t`)):
|
||||
const `ident` = ignoredKeyLists[`ignoredKeyListproc`(`t`)]
|
||||
proc hasIgnore(t: typedesc): bool {.compileTime.} =
|
||||
when compiles(t.hasCustomPragma(ignore)):
|
||||
return t.hasCustomPragma(ignore)
|
||||
else:
|
||||
const `ident` = newSeq[string]()
|
||||
|
||||
macro injectFailOnUnknownKeys(t: typedesc, ident: untyped) =
|
||||
result = quote do:
|
||||
when compiles(`ignoreUnknownKeysProc`(`t`)):
|
||||
const `ident` = false
|
||||
else:
|
||||
const `ident` = true
|
||||
return false
|
||||
|
||||
proc constructObjectDefault*[O: object|tuple](
|
||||
s: var YamlStream, c: ConstructionContext, result: var O)
|
||||
|
@ -882,12 +839,14 @@ proc constructObjectDefault*[O: object|tuple](
|
|||
const
|
||||
startKind = when isVariantObject(getType(O)): yamlStartSeq else: yamlStartMap
|
||||
endKind = when isVariantObject(getType(O)): yamlEndSeq else: yamlEndMap
|
||||
fetchTransientIndex(O, tIndex)
|
||||
if e.kind != startKind:
|
||||
raise s.constructionError("While constructing " &
|
||||
typetraits.name(O) & ": Expected " & $startKind & ", got " & $e.kind)
|
||||
injectIgnoredKeyList(O, ignoredKeyList)
|
||||
injectFailOnUnknownKeys(O, failOnUnknown)
|
||||
when hasIgnore(O):
|
||||
const ignoredKeyList = O.getCustomPragmaVal(ignore)
|
||||
const failOnUnknown = len(ignoredKeyList) > 0
|
||||
else:
|
||||
const failOnUnknown = true
|
||||
while s.peek.kind != endKind:
|
||||
e = s.next()
|
||||
when isVariantObject(getType(O)):
|
||||
|
@ -915,9 +874,9 @@ proc constructObjectDefault*[O: object|tuple](
|
|||
raise s.constructionError("While constructing " &
|
||||
typetraits.name(O) & ": Unknown field: " & escape(name))
|
||||
else:
|
||||
when hasIgnore(O) and failOnUnknown:
|
||||
if name notin ignoredKeyList:
|
||||
constructFieldValue(O, tIndex, s, c, name, result, matched,
|
||||
failOnUnknown)
|
||||
constructFieldValue(O, s, c, name, result, matched, failOnUnknown)
|
||||
else:
|
||||
e = s.next()
|
||||
var depth = int(e.kind in {yamlStartMap, yamlStartSeq})
|
||||
|
@ -927,6 +886,8 @@ proc constructObjectDefault*[O: object|tuple](
|
|||
of yamlEndMap, yamlEndSeq: dec(depth)
|
||||
of yamlScalar: discard
|
||||
else: internalError("Unexpected event kind.")
|
||||
else:
|
||||
constructFieldValue(O, s, c, name, result, matched, failOnUnknown)
|
||||
when isVariantObject(getType(O)):
|
||||
e = s.next()
|
||||
if e.kind != yamlEndMap:
|
||||
|
@ -940,7 +901,7 @@ proc constructObjectDefault*[O: object|tuple](
|
|||
raise s.constructionError("While constructing " &
|
||||
typetraits.name(O) & ": Missing field: " & escape(fname))
|
||||
inc(i)
|
||||
else: ensureAllFieldsPresent(s, O, tIndex, result, matched)
|
||||
else: ensureAllFieldsPresent(s, O, result, matched)
|
||||
|
||||
proc constructObject*[O: object|tuple](
|
||||
s: var YamlStream, c: ConstructionContext, result: var O)
|
||||
|
@ -950,13 +911,6 @@ proc constructObject*[O: object|tuple](
|
|||
|
||||
macro genRepresentObject(t: typedesc, value, childTagStyle: 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])
|
||||
|
@ -998,7 +952,7 @@ macro genRepresentObject(t: typedesc, value, childTagStyle: typed) =
|
|||
name = $item
|
||||
itemAccessor = newDotExpr(value, newIdentNode(name))
|
||||
curStmtList.add(quote do:
|
||||
when `tSym` == -1 or `fieldIndex` notin transientVectors[`tSym`]:
|
||||
when not `itemAccessor`.hasCustomPragma(transient):
|
||||
c.put(startMapEvent(yTagQuestionMark, yAnchorNone))
|
||||
c.put(scalarEvent(`name`, if `childTagStyle` == tsNone:
|
||||
yTagQuestionMark else: yTagNimField, yAnchorNone))
|
||||
|
@ -1015,7 +969,7 @@ macro genRepresentObject(t: typedesc, value, childTagStyle: typed) =
|
|||
name = $child
|
||||
childAccessor = newDotExpr(value, newIdentNode(name))
|
||||
result.add(quote do:
|
||||
when `tSym` == -1 or `fieldIndex` notin transientVectors[`tSym`]:
|
||||
when not `childAccessor`.hasCustomPragma(transient):
|
||||
when bool(`isVO`): c.put(startMapEvent(yTagQuestionMark, yAnchorNone))
|
||||
c.put(scalarEvent(`name`, if `childTagStyle` == tsNone:
|
||||
yTagQuestionMark else: yTagNimField, yAnchorNone))
|
||||
|
@ -1110,13 +1064,33 @@ macro constructImplicitVariantObject(s, c, r, possibleTagIds: untyped,
|
|||
newNimNode(nnkDiscardStmt).add(newEmptyNode())
|
||||
))))
|
||||
|
||||
macro isImplicitVariantObject(o: typed): untyped =
|
||||
result = newCall("compiles", newCall(implicitVariantObjectMarker, o))
|
||||
proc isImplicitVariantObject(t: typedesc): bool {.compileTime.} =
|
||||
when compiles(t.hasCustomPragma(implicit)):
|
||||
return t.hasCustomPragma(implicit)
|
||||
else:
|
||||
return false
|
||||
|
||||
proc canBeImplicit(t: typedesc): bool {.compileTime.} =
|
||||
let tDesc = getType(t)
|
||||
if tDesc.kind != nnkObjectTy: return false
|
||||
if tDesc[2].len != 1: return false
|
||||
if tDesc[2][0].kind != nnkRecCase: return false
|
||||
var foundEmptyBranch = false
|
||||
for i in 1.. tDesc[2][0].len - 1:
|
||||
case tDesc[2][0][i][1].recListlen # branch contents
|
||||
of 0:
|
||||
if foundEmptyBranch: return false
|
||||
else: foundEmptyBranch = true
|
||||
of 1: discard
|
||||
else: return false
|
||||
return true
|
||||
|
||||
proc constructChild*[T](s: var YamlStream, c: ConstructionContext,
|
||||
result: var T) =
|
||||
let item = s.peek()
|
||||
when isImplicitVariantObject(result):
|
||||
when isImplicitVariantObject(T):
|
||||
when not canBeImplicit(T):
|
||||
{. fatal: "This type cannot be marked as implicit" .}
|
||||
var possibleTagIds = newSeq[TagId]()
|
||||
case item.kind
|
||||
of yamlScalar:
|
||||
|
@ -1348,7 +1322,7 @@ proc representChild*[T](value: Option[T], ts: TagStyle,
|
|||
|
||||
proc representChild*[O](value: O, ts: TagStyle,
|
||||
c: SerializationContext) =
|
||||
when isImplicitVariantObject(value):
|
||||
when isImplicitVariantObject(O):
|
||||
# todo: this would probably be nicer if constructed with a macro
|
||||
var count = 0
|
||||
for name, field in fieldPairs(value):
|
||||
|
@ -1467,185 +1441,3 @@ proc dump*[K](value: K, tagStyle: TagStyle = tsRootOnly,
|
|||
try: result = present(events, serializationTagLibrary, options)
|
||||
except YamlStreamError:
|
||||
internalError("Unexpected exception: " & $getCurrentException().name)
|
||||
|
||||
proc canBeImplicit(t: typedesc): bool {.compileTime.} =
|
||||
let tDesc = getType(t)
|
||||
if tDesc.kind != nnkObjectTy: return false
|
||||
if tDesc[2].len != 1: return false
|
||||
if tDesc[2][0].kind != nnkRecCase: return false
|
||||
var foundEmptyBranch = false
|
||||
for i in 1.. tDesc[2][0].len - 1:
|
||||
case tDesc[2][0][i][1].recListlen # branch contents
|
||||
of 0:
|
||||
if foundEmptyBranch: return false
|
||||
else: foundEmptyBranch = true
|
||||
of 1: discard
|
||||
else: return false
|
||||
return true
|
||||
|
||||
macro setImplicitVariantObjectMarker(t: typedesc): untyped =
|
||||
result = quote do:
|
||||
when compiles(`transientBitvectorProc`(`t`)):
|
||||
{.fatal: "Cannot mark object with transient fields as implicit".}
|
||||
proc `implicitVariantObjectMarker`*(unused: `t`) = discard
|
||||
|
||||
template markAsImplicit*(t: typedesc) =
|
||||
## Mark a variant object type as implicit. This requires the type to consist
|
||||
## of nothing but a case expression and each branch of the case expression
|
||||
## containing exactly one field - with the exception that one branch may
|
||||
## contain zero fields.
|
||||
when canBeImplicit(t):
|
||||
# this will be checked by means of compiles(implicitVariantObject(...))
|
||||
setImplicitVariantObjectMarker(t)
|
||||
else:
|
||||
{. fatal: "This type cannot be marked as implicit" .}
|
||||
|
||||
macro getFieldIndex(t: typedesc, field: untyped): untyped =
|
||||
let
|
||||
tDecl = getType(t)
|
||||
tName = $tDecl[1]
|
||||
tDesc = getType(tDecl[1])
|
||||
fieldName = $field
|
||||
var
|
||||
fieldIndex = 0
|
||||
found = false
|
||||
block outer:
|
||||
for child in tDesc[2].children:
|
||||
if child.kind == nnkRecCase:
|
||||
for bIndex in 1 .. len(child) - 1:
|
||||
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)
|
||||
for item in child[bIndex][bChildIndex].recListItems:
|
||||
inc(fieldIndex)
|
||||
yAssert item.kind == nnkSym
|
||||
if $item == fieldName:
|
||||
found = true
|
||||
break outer
|
||||
else:
|
||||
yAssert child.kind == nnkSym
|
||||
if $child == fieldName:
|
||||
found = true
|
||||
break
|
||||
inc(fieldIndex)
|
||||
if not found:
|
||||
let msg = "Type " & tName & " has no field " & fieldName & '!'
|
||||
result = quote do: {.fatal: `msg`.}
|
||||
else:
|
||||
result = newNimNode(nnkInt16Lit)
|
||||
result.intVal = fieldIndex
|
||||
|
||||
proc fieldIdent(field: NimNode): NimNode {.compileTime.} =
|
||||
case field.kind
|
||||
of nnkIdent: result = field
|
||||
of nnkAccQuoted: result = field[0]
|
||||
else:
|
||||
raise newException(Exception, "invalid node type (expected ident):" &
|
||||
$field.kind)
|
||||
|
||||
macro markAsTransient*(t: typedesc, field: untyped) =
|
||||
## Mark an object field as *transient*, meaning that this object field will
|
||||
## not be serialized when an object instance is dumped as YAML, and also that
|
||||
## the field is not expected to be given in YAML input that is loaded to an
|
||||
## object instance.
|
||||
##
|
||||
## Example usage:
|
||||
##
|
||||
## .. code-block::
|
||||
## type MyObject = object
|
||||
## a, b: string
|
||||
## c: int
|
||||
## markAsTransient(MyObject, a)
|
||||
## markAsTransient(MyObject, c)
|
||||
##
|
||||
## This does not work if the object has been marked as implicit.
|
||||
let
|
||||
nextBitvectorIndex = transientVectors.len
|
||||
fieldName = fieldIdent(field)
|
||||
result = quote do:
|
||||
when compiles(`implicitVariantObjectMarker`(`t`)):
|
||||
{.fatal: "Cannot mark fields of implicit variant objects as transient." .}
|
||||
when not compiles(`transientBitvectorProc`(`t`)):
|
||||
proc `transientBitvectorProc`*(myType: typedesc[`t`]): int
|
||||
{.compileTime.} =
|
||||
`nextBitvectorIndex`
|
||||
static: transientVectors.add({})
|
||||
static:
|
||||
transientVectors[`transientBitvectorProc`(`t`)].incl(
|
||||
getFieldIndex(`t`, `fieldName`))
|
||||
|
||||
macro setDefaultValue*(t: typedesc, field: untyped, value: typed) =
|
||||
## Set the default value of an object field. Fields with default values may
|
||||
## be absent in YAML input when loading an instance of the object. If the
|
||||
## field is absent in the YAML input, the default value is assigned to the
|
||||
## field.
|
||||
##
|
||||
## Example usage:
|
||||
##
|
||||
## .. code-block::
|
||||
## type MyObject = object
|
||||
## a, b: string
|
||||
## c: tuple[x, y: int]
|
||||
## setDefaultValue(MyObject, a, "foo")
|
||||
## setDefaultValue(MyObject, c, (1, 2))
|
||||
let
|
||||
dSym = genSym(nskVar, ":default")
|
||||
nextBitvectorIndex = defaultVectors.len
|
||||
fieldName = fieldIdent(field)
|
||||
result = quote do:
|
||||
when compiles(`transientBitvectorProc`(`t`)):
|
||||
{.fatal: "Cannot set default value of transient field".}
|
||||
elif compiles(`implicitVariantObjectMarker`(`t`)):
|
||||
{.fatal: "Cannot set default value of implicit variant objects.".}
|
||||
when not compiles(`defaultValueGetter`(`t`)):
|
||||
var `dSym` {.compileTime.}: `t`
|
||||
template `defaultValueGetter`(t: typedesc[`t`]): auto =
|
||||
`dSym`
|
||||
proc `defaultBitvectorProc`*(myType: typedesc[`t`]): int
|
||||
{.compileTime.} = `nextBitvectorIndex`
|
||||
static: defaultVectors.add({})
|
||||
static:
|
||||
`defaultValueGetter`(`t`).`fieldName` = `value`
|
||||
defaultVectors[`defaultBitvectorProc`(`t`)].incl(getFieldIndex(`t`, `fieldName`))
|
||||
|
||||
macro ignoreInputKey*(t: typedesc, name: string{lit}) =
|
||||
## Tell NimYAML that when loading an object of type ``t``, any mapping key
|
||||
## named ``name`` shall be ignored. Note that this even ignores the key if
|
||||
## the value of that key is necessary to construct a value of type ``t``,
|
||||
## making deserialization fail.
|
||||
##
|
||||
## Example usage:
|
||||
##
|
||||
## .. code-block::
|
||||
## type MyObject = object
|
||||
## a, b: string
|
||||
## ignoreInputKey(MyObject, "c")
|
||||
let nextIgnoredKeyList = ignoredKeyLists.len
|
||||
result = quote do:
|
||||
when not compiles(`ignoredKeyListProc`(`t`)):
|
||||
proc `ignoredKeyListProc`*(t: typedesc[`t`]): int {.compileTime.} =
|
||||
`nextIgnoredKeyList`
|
||||
static: ignoredKeyLists.add(@[])
|
||||
when `name` in ignoredKeyLists[`ignoredKeyListProc`(`t`)]:
|
||||
{.fatal: "Input key " & `name` & " is already ignored!".}
|
||||
static:
|
||||
ignoredKeyLists[`ignoredKeyListProc`(`t`)].add(`name`)
|
||||
|
||||
macro ignoreUnknownKeys*(t: typedesc) =
|
||||
## Tell NimYAML that when loading an object or tuple of type ``t``, any
|
||||
## mapping key that does not map to an existing field inside the object or
|
||||
## tuple shall be ignored.
|
||||
##
|
||||
## Example usage:
|
||||
##
|
||||
## .. code-block::
|
||||
## type MyObject = object
|
||||
## a, b: string
|
||||
## ignoreUnknownKeys(MyObject)
|
||||
result = quote do:
|
||||
proc `ignoreUnknownKeysProc`*(t: typedesc[`t`]): bool {.compileTime.} = true
|
||||
|
|
Loading…
Reference in New Issue