Implemented Option serialization. Fixes #78

This commit is contained in:
flyx 2020-03-08 19:58:02 +01:00
parent f714881ae9
commit 1dfc2a3333
4 changed files with 61 additions and 25 deletions

View File

@ -5,7 +5,7 @@
# distribution, for details about the copyright. # distribution, for details about the copyright.
import "../yaml" import "../yaml"
import unittest, strutils, tables, times, math import unittest, strutils, tables, times, math, options
type type
MyTuple = tuple MyTuple = tuple
@ -270,6 +270,19 @@ suite "Serialization":
var output = dump(input, tsNone, asTidy, blockOnly) var output = dump(input, tsNone, asTidy, blockOnly)
assertStringEqual yamlDirs & "\n- 23\n- 42\n- 47", output assertStringEqual yamlDirs & "\n- 23\n- 42\n- 47", output
test "Load Option":
let input = "- Some\n- !!null ~"
var result: array[0..1, Option[string]]
load(input, result)
assert result[0].isSome
assert result[0].get() == "Some"
assert not result[1].isSome
test "Dump Option":
let input = [none(int32), some(42'i32), none(int32)]
let output = dump(input, tsNone, asTidy, blockOnly)
assertStringEqual yamlDirs & "\n- !!null ~\n- 42\n- !!null ~", output
test "Load Table[int, string]": test "Load Table[int, string]":
let input = "23: dreiundzwanzig\n42: zweiundvierzig" let input = "23: dreiundzwanzig\n42: zweiundvierzig"
var result: Table[int32, string] var result: Table[int32, string]

View File

@ -358,14 +358,14 @@ template capitalize(s: string): string =
when declared(strutils.capitalizeAscii): strutils.capitalizeAscii(s) when declared(strutils.capitalizeAscii): strutils.capitalizeAscii(s)
else: strutils.capitalize(s) else: strutils.capitalize(s)
macro parserStates(names: varargs[untyped]): typed = macro parserStates(names: varargs[untyped]) =
## generates proc declaration for each state in list like this: ## generates proc declaration for each state in list like this:
## ##
## proc name(s: YamlStream, e: var YamlStreamEvent): ## proc name(s: YamlStream, e: var YamlStreamEvent):
## bool {.raises: [YamlParserError].} ## bool {.raises: [YamlParserError].}
result = newStmtList() result = newStmtList()
for name in names: for name in names:
let nameId = newIdentNode("state" & capitalize($name.ident)) let nameId = newIdentNode("state" & capitalize(name.strVal))
result.add(newProc(nameId, [ident("bool"), newIdentDefs(ident("s"), result.add(newProc(nameId, [ident("bool"), newIdentDefs(ident("s"),
ident("YamlStream")), newIdentDefs(ident("e"), newNimNode(nnkVarTy).add( ident("YamlStream")), newIdentDefs(ident("e"), newNimNode(nnkVarTy).add(
ident("YamlStreamEvent")))], newEmptyNode())) ident("YamlStreamEvent")))], newEmptyNode()))
@ -378,21 +378,21 @@ proc processStateAsgns(source, target: NimNode) {.compileTime.} =
## `state = [name]` with the appropriate code for changing states. ## `state = [name]` with the appropriate code for changing states.
for child in source.children: for child in source.children:
if child.kind == nnkAsgn and child[0].kind == nnkIdent: if child.kind == nnkAsgn and child[0].kind == nnkIdent:
if $child[0].ident == "state": if child[0].strVal == "state":
assert child[1].kind == nnkIdent assert child[1].kind == nnkIdent
var newNameId: NimNode var newNameId: NimNode
if child[1].kind == nnkIdent and $child[1].ident == "stored": if child[1].kind == nnkIdent and child[1].strVal == "stored":
newNameId = newDotExpr(ident("c"), ident("storedState")) newNameId = newDotExpr(ident("c"), ident("storedState"))
else: else:
newNameId = newNameId =
newIdentNode("state" & capitalize($child[1].ident)) newIdentNode("state" & capitalize(child[1].strVal))
target.add(newAssignment(newDotExpr( target.add(newAssignment(newDotExpr(
newIdentNode("s"), newIdentNode("nextImpl")), newNameId)) newIdentNode("s"), newIdentNode("nextImpl")), newNameId))
continue continue
elif $child[0].ident == "stored": elif child[0].strVal == "stored":
assert child[1].kind == nnkIdent assert child[1].kind == nnkIdent
let newNameId = let newNameId =
newIdentNode("state" & capitalize($child[1].ident)) newIdentNode("state" & capitalize(child[1].strVal))
target.add(newAssignment(newDotExpr(newIdentNode("c"), target.add(newAssignment(newDotExpr(newIdentNode("c"),
newIdentNode("storedState")), newNameId)) newIdentNode("storedState")), newNameId))
continue continue
@ -400,7 +400,7 @@ proc processStateAsgns(source, target: NimNode) {.compileTime.} =
processStateAsgns(child, processed) processStateAsgns(child, processed)
target.add(processed) target.add(processed)
macro parserState(name: untyped, impl: untyped): typed = macro parserState(name: untyped, impl: untyped) =
## Creates a parser state. Every parser state is a proc with the signature ## Creates a parser state. Every parser state is a proc with the signature
## ##
## proc(s: YamlStream, e: var YamlStreamEvent): ## proc(s: YamlStream, e: var YamlStreamEvent):
@ -413,7 +413,7 @@ macro parserState(name: untyped, impl: untyped): typed =
## `c`. You can change the parser state by a assignment `state = [newState]`. ## `c`. You can change the parser state by a assignment `state = [newState]`.
## The [newState] must have been declared with states(...) previously. ## The [newState] must have been declared with states(...) previously.
let let
nameStr = $name.ident nameStr = name.strVal
nameId = newIdentNode("state" & capitalize(nameStr)) nameId = newIdentNode("state" & capitalize(nameStr))
var procImpl = quote do: var procImpl = quote do:
debug("state: " & `nameStr`) debug("state: " & `nameStr`)

View File

@ -16,7 +16,7 @@
## type. Please consult the serialization guide on the NimYAML website for more ## type. Please consult the serialization guide on the NimYAML website for more
## information. ## information.
import tables, typetraits, strutils, macros, streams, times, parseutils import tables, typetraits, strutils, macros, streams, times, parseutils, options
import parser, taglib, presenter, stream, private/internal, hints import parser, taglib, presenter, stream, private/internal, hints
export stream export stream
# *something* in here needs externally visible `==`(x,y: AnchorId), # *something* in here needs externally visible `==`(x,y: AnchorId),
@ -708,7 +708,7 @@ proc ifNotTransient(tSym: NimNode, fieldIndex: int, content: openarray[NimNode],
)) ))
macro ensureAllFieldsPresent(s: YamlStream, t: typedesc, tIndex: int, o: typed, macro ensureAllFieldsPresent(s: YamlStream, t: typedesc, tIndex: int, o: typed,
matched: typed): typed = matched: typed) =
let let
dbp = defaultBitvectorProc dbp = defaultBitvectorProc
defaultValues = genSym(nskConst, "defaultValues") defaultValues = genSym(nskConst, "defaultValues")
@ -751,7 +751,7 @@ macro ensureAllFieldsPresent(s: YamlStream, t: typedesc, tIndex: int, o: typed,
[checkMissing(s, t, tName, child, field, matched, o, defaultValues)])) [checkMissing(s, t, tName, child, field, matched, o, defaultValues)]))
inc(field) inc(field)
macro fetchTransientIndex(t: typedesc, tIndex: untyped): typed = macro fetchTransientIndex(t: typedesc, tIndex: untyped) =
quote do: quote do:
when compiles(`transientBitvectorProc`(`t`)): when compiles(`transientBitvectorProc`(`t`)):
const `tIndex` = `transientBitvectorProc`(`t`) const `tIndex` = `transientBitvectorProc`(`t`)
@ -760,7 +760,7 @@ macro fetchTransientIndex(t: typedesc, tIndex: untyped): typed =
macro constructFieldValue(t: typedesc, tIndex: int, stream: untyped, macro constructFieldValue(t: typedesc, tIndex: int, stream: untyped,
context: untyped, name: untyped, o: untyped, context: untyped, name: untyped, o: untyped,
matched: untyped, failOnUnknown: bool): typed = matched: untyped, failOnUnknown: bool) =
let let
tDecl = getType(t) tDecl = getType(t)
tName = $tDecl[1] tName = $tDecl[1]
@ -855,14 +855,14 @@ proc isVariantObject(t: NimNode): bool {.compileTime.} =
if child.kind == nnkRecCase: return true if child.kind == nnkRecCase: return true
return false return false
macro injectIgnoredKeyList(t: typedesc, ident: untyped): typed = macro injectIgnoredKeyList(t: typedesc, ident: untyped) =
result = quote do: result = quote do:
when compiles(`ignoredKeyListProc`(`t`)): when compiles(`ignoredKeyListProc`(`t`)):
const `ident` = ignoredKeyLists[`ignoredKeyListproc`(`t`)] const `ident` = ignoredKeyLists[`ignoredKeyListproc`(`t`)]
else: else:
const `ident` = newSeq[string]() const `ident` = newSeq[string]()
macro injectFailOnUnknownKeys(t: typedesc, ident: untyped): typed = macro injectFailOnUnknownKeys(t: typedesc, ident: untyped) =
result = quote do: result = quote do:
when compiles(`ignoreUnknownKeysProc`(`t`)): when compiles(`ignoreUnknownKeysProc`(`t`)):
const `ident` = false const `ident` = false
@ -948,7 +948,7 @@ proc constructObject*[O: object|tuple](
## Overridable default implementation for custom object and tuple types ## Overridable default implementation for custom object and tuple types
constructObjectDefault(s, c, result) constructObjectDefault(s, c, result)
macro genRepresentObject(t: typedesc, value, childTagStyle: typed): typed = macro genRepresentObject(t: typedesc, value, childTagStyle: typed) =
result = newStmtList() result = newStmtList()
let tSym = genSym(nskConst, ":tSym") let tSym = genSym(nskConst, ":tSym")
result.add(quote do: result.add(quote do:
@ -1068,7 +1068,7 @@ proc representObject*[O: enum](value: O, ts: TagStyle,
proc yamlTag*[O](T: typedesc[ref O]): TagId {.inline, raises: [].} = yamlTag(O) proc yamlTag*[O](T: typedesc[ref O]): TagId {.inline, raises: [].} = yamlTag(O)
macro constructImplicitVariantObject(s, c, r, possibleTagIds: untyped, macro constructImplicitVariantObject(s, c, r, possibleTagIds: untyped,
t: typedesc): typed = t: typedesc) =
let tDesc = getType(getType(t)[1]) let tDesc = getType(getType(t)[1])
yAssert tDesc.kind == nnkObjectTy yAssert tDesc.kind == nnkObjectTy
let recCase = tDesc[2][0] let recCase = tDesc[2][0]
@ -1198,6 +1198,19 @@ proc constructChild*[T](s: var YamlStream, c: ConstructionContext,
raise s.constructionError("Anchor on non-ref type") raise s.constructionError("Anchor on non-ref type")
constructObject(s, c, result) constructObject(s, c, result)
proc constructChild*[T](s: var YamlStream, c: ConstructionContext,
result: var Option[T]) =
## constructs an optional value. A value with a !!null tag will be loaded
## an empty value.
let event = s.peek()
if event.kind == yamlScalar and event.scalarTag == yTagNull:
result = none(T)
discard s.next()
else:
var inner: T
constructChild(s, c, inner)
result = some(inner)
when defined(JS): when defined(JS):
# in JS, Time is a ref type. Therefore, we need this specialization so that # in JS, Time is a ref type. Therefore, we need this specialization so that
# it is not handled by the general ref-type handler. # it is not handled by the general ref-type handler.
@ -1323,6 +1336,16 @@ proc representChild*[O](value: ref O, ts: TagStyle, c: SerializationContext) =
c.put(ex) c.put(ex)
representChild(value[], childTagStyle, c) representChild(value[], childTagStyle, c)
proc representChild*[T](value: Option[T], ts: TagStyle,
c: SerializationContext) =
## represents an optional value. If the value is missing, a !!null scalar
## will be produced.
if value.isSome:
representChild(value.get(), ts, c)
else:
let childTagStyle = if ts == tsRootOnly: tsNone else: ts
c.put(scalarEvent("~", yTagNull))
proc representChild*[O](value: O, ts: TagStyle, proc representChild*[O](value: O, ts: TagStyle,
c: SerializationContext) = c: SerializationContext) =
when isImplicitVariantObject(value): when isImplicitVariantObject(value):
@ -1466,7 +1489,7 @@ macro setImplicitVariantObjectMarker(t: typedesc): untyped =
{.fatal: "Cannot mark object with transient fields as implicit".} {.fatal: "Cannot mark object with transient fields as implicit".}
proc `implicitVariantObjectMarker`*(unused: `t`) = discard proc `implicitVariantObjectMarker`*(unused: `t`) = discard
template markAsImplicit*(t: typedesc): typed = template markAsImplicit*(t: typedesc) =
## Mark a variant object type as implicit. This requires the type to consist ## 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 ## of nothing but a case expression and each branch of the case expression
## containing exactly one field - with the exception that one branch may ## containing exactly one field - with the exception that one branch may
@ -1525,7 +1548,7 @@ proc fieldIdent(field: NimNode): NimNode {.compileTime.} =
raise newException(Exception, "invalid node type (expected ident):" & raise newException(Exception, "invalid node type (expected ident):" &
$field.kind) $field.kind)
macro markAsTransient*(t: typedesc, field: untyped): typed = macro markAsTransient*(t: typedesc, field: untyped) =
## Mark an object field as *transient*, meaning that this object field will ## 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 ## 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 ## the field is not expected to be given in YAML input that is loaded to an
@ -1556,7 +1579,7 @@ macro markAsTransient*(t: typedesc, field: untyped): typed =
transientVectors[`transientBitvectorProc`(`t`)].incl( transientVectors[`transientBitvectorProc`(`t`)].incl(
getFieldIndex(`t`, `fieldName`)) getFieldIndex(`t`, `fieldName`))
macro setDefaultValue*(t: typedesc, field: untyped, value: typed): typed = macro setDefaultValue*(t: typedesc, field: untyped, value: typed) =
## Set the default value of an object field. Fields with default values may ## 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 ## 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 is absent in the YAML input, the default value is assigned to the
@ -1590,7 +1613,7 @@ macro setDefaultValue*(t: typedesc, field: untyped, value: typed): typed =
`defaultValueGetter`(`t`).`fieldName` = `value` `defaultValueGetter`(`t`).`fieldName` = `value`
defaultVectors[`defaultBitvectorProc`(`t`)].incl(getFieldIndex(`t`, `fieldName`)) defaultVectors[`defaultBitvectorProc`(`t`)].incl(getFieldIndex(`t`, `fieldName`))
macro ignoreInputKey*(t: typedesc, name: string{lit}): typed = macro ignoreInputKey*(t: typedesc, name: string{lit}) =
## Tell NimYAML that when loading an object of type ``t``, any mapping key ## 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 ## 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``, ## the value of that key is necessary to construct a value of type ``t``,
@ -1613,7 +1636,7 @@ macro ignoreInputKey*(t: typedesc, name: string{lit}): typed =
static: static:
ignoredKeyLists[`ignoredKeyListProc`(`t`)].add(`name`) ignoredKeyLists[`ignoredKeyListProc`(`t`)].add(`name`)
macro ignoreUnknownKeys*(t: typedesc): typed = macro ignoreUnknownKeys*(t: typedesc) =
## Tell NimYAML that when loading an object or tuple of type ``t``, any ## 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 ## mapping key that does not map to an existing field inside the object or
## tuple shall be ignored. ## tuple shall be ignored.

View File

@ -224,7 +224,7 @@ var
## registered URIs here to be able to generate a static compiler error ## registered URIs here to be able to generate a static compiler error
## when the user tries to register an URI more than once. ## when the user tries to register an URI more than once.
template setTagUri*(t: typedesc, uri: string): typed = template setTagUri*(t: typedesc, uri: string) =
## Associate the given uri with a certain type. This uri is used as YAML tag ## Associate the given uri with a certain type. This uri is used as YAML tag
## when loading and dumping values of this type. ## when loading and dumping values of this type.
when uri in registeredUris: when uri in registeredUris:
@ -239,7 +239,7 @@ template setTagUri*(t: typedesc, uri: string): typed =
proc yamlTag*(T: typedesc[t]): TagId {.inline, raises: [].} = id proc yamlTag*(T: typedesc[t]): TagId {.inline, raises: [].} = id
## autogenerated ## autogenerated
template setTagUri*(t: typedesc, uri: string, idName: untyped): typed = template setTagUri*(t: typedesc, uri: string, idName: untyped) =
## Like `setTagUri <#setTagUri.t,typedesc,string>`_, but lets ## Like `setTagUri <#setTagUri.t,typedesc,string>`_, but lets
## you choose a symbol for the `TagId <#TagId>`_ of the uri. This is only ## you choose a symbol for the `TagId <#TagId>`_ of the uri. This is only
## necessary if you want to implement serialization / construction yourself. ## necessary if you want to implement serialization / construction yourself.