added {.sparse.}. fixes #82

This commit is contained in:
Felix Krause 2020-06-26 23:21:22 +02:00
parent e4b3e8347a
commit 951efdf18e
4 changed files with 59 additions and 7 deletions

View File

@ -93,6 +93,15 @@ You can simply load it into a `seq[int]`. If your YAML file contains differently
typed values in the same collection, you can use an implicit variant object, see
below.
A special case is ``Option[T]``: This type will either contain a value or not.
NimYAML maps ``!!null`` YAML scalars to the option's ``none(T)`` value.
This also works for ``ref`` types because ``Option`` for those types will use
``nil`` as its ``none(T)`` value.
By default, ``Option`` fields must be given even if they are ``none(T)``.
You can circumvent this by putting the annotation ``{.sparse.}`` on the type
containing the ``Option`` field.
Reference Types
---------------

View File

@ -25,6 +25,10 @@ type
strVal: string
of ckInt:
intVal: int
Sparse {.sparse.} = ref object of RootObj
name*: Option[string]
description*: Option[string]
suite "Serialization Annotations":
test "load default value":
@ -59,4 +63,11 @@ suite "Serialization Annotations":
load(input, result)
assert len(result) == 2
assert result[0].kind == ckString
assert result[1].kind == ckInt
assert result[1].kind == ckInt
test "load sparse type":
let input = "{}"
var result: Sparse
load(input, result)
assert result.name.isNone
assert result.description.isNone

View File

@ -12,7 +12,7 @@
## (de)serialization behavior of those fields.
template defaultVal*(value : typed) {.pragma.}
## This annotation can be assigned to an object field. During deserialization,
## This annotation can be put on an object field. During deserialization,
## if no value for this field is given, the ``value`` parameter of this
## annotation is used as value.
##
@ -23,6 +23,19 @@ template defaultVal*(value : typed) {.pragma.}
## a {.defaultVal: "foo".}: string
## c {.defaultVal: (1,2).}: tuple[x, y: int]
template sparse*() {.pragma.}
## This annotation can be put on an object type. During deserialization,
## the input may omit any field that has an ``Option[T]`` type (for any
## concrete ``T``) and that field will be treated as if it had the annotation
## ``{.defaultVal: none(T).}``.
##
## Example usage:
##
## .. code-block::
## type MyObject {.sparse.} = object
## a: Option[string]
## b: Option[int]
template transient*() {.pragma.}
## This annotation can be put on an object field. Any object field
## carrying this annotation will not be serialized to YAML and cannot be given

View File

@ -1,5 +1,5 @@
# NimYAML - YAML implementation in Nim
# (c) Copyright 2016 Felix Krause
# (c) Copyright 2016 - 2020 Felix Krause
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
@ -18,7 +18,7 @@
import tables, typetraits, strutils, macros, streams, times, parseutils, options
import parser, taglib, presenter, stream, private/internal, hints, annotations
export stream, macros, annotations
export stream, macros, annotations, options
# *something* in here needs externally visible `==`(x,y: AnchorId),
# but I cannot figure out what. binding it would be the better option.
@ -656,15 +656,32 @@ proc addDefaultOr(tName: string, i: int, o: NimNode,
`o`.`field` = `o`.`field`.getCustomPragmaVal(defaultVal)
else: `elseBranch`
proc hasSparse(t: typedesc): bool {.compileTime.} =
when compiles(t.hasCustomPragma(sparse)):
return t.hasCustomPragma(sparse)
else:
return false
proc getOptionInner(fType: NimNode): NimNode {.compileTime.} =
if fType.kind == nnkBracketExpr and len(fType) == 2 and
fType[1].kind == nnkSym:
return newIdentNode($fType[1])
else: return nil
proc checkMissing(s: NimNode, t: NimNode, tName: string, field: NimNode,
i: int, matched, o: NimNode):
NimNode {.compileTime.} =
let fName = escape($field)
let
fType = getTypeInst(field)
fName = escape($field)
optionInner = getOptionInner(fType)
result = quote do:
when not `o`.`field`.hasCustomPragma(transient):
if not `matched`[`i`]:
when `o`.`field`.hasCustomPragma(defaultVal):
`o`.`field` = `o`.`field`.getCustomPragmaVal(defaultVal)
elif hasSparse(`t`) and `o`.`field` is Option:
`o`.`field` = none(`optionInner`)
else:
raise constructionError(`s`, "While constructing " & `tName` &
": Missing field: " & `fName`)
@ -700,7 +717,8 @@ macro ensureAllFieldsPresent(s: YamlStream, t: typedesc, 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))
result.add(checkMissing(
s, t, tName, child[0], field, matched, o))
for bIndex in 1 .. len(child) - 1:
let discChecks = newStmtList()
var
@ -716,7 +734,8 @@ macro ensureAllFieldsPresent(s: YamlStream, t: typedesc, 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))
discChecks.add(checkMissing(
s, t, tName, item, field, matched, o))
result.add(newIfStmt((infix(newDotExpr(o, newIdentNode($child[0])),
"in", curValues), discChecks)))
else: