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 typed values in the same collection, you can use an implicit variant object, see
below. 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 Reference Types
--------------- ---------------

View File

@ -25,6 +25,10 @@ type
strVal: string strVal: string
of ckInt: of ckInt:
intVal: int intVal: int
Sparse {.sparse.} = ref object of RootObj
name*: Option[string]
description*: Option[string]
suite "Serialization Annotations": suite "Serialization Annotations":
test "load default value": test "load default value":
@ -59,4 +63,11 @@ suite "Serialization Annotations":
load(input, result) load(input, result)
assert len(result) == 2 assert len(result) == 2
assert result[0].kind == ckString 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. ## (de)serialization behavior of those fields.
template defaultVal*(value : typed) {.pragma.} 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 ## if no value for this field is given, the ``value`` parameter of this
## annotation is used as value. ## annotation is used as value.
## ##
@ -23,6 +23,19 @@ template defaultVal*(value : typed) {.pragma.}
## a {.defaultVal: "foo".}: string ## a {.defaultVal: "foo".}: string
## c {.defaultVal: (1,2).}: tuple[x, y: int] ## 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.} template transient*() {.pragma.}
## This annotation can be put on an object field. Any object field ## 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 ## carrying this annotation will not be serialized to YAML and cannot be given

View File

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