Skip to content

Commit

Permalink
Implemented Option serialization. Fixes #78
Browse files Browse the repository at this point in the history
  • Loading branch information
flyx committed Mar 8, 2020
1 parent f714881 commit 1dfc2a3
Show file tree
Hide file tree
Showing 4 changed files with 61 additions and 25 deletions.
15 changes: 14 additions & 1 deletion test/tserialization.nim
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
# distribution, for details about the copyright.

import "../yaml"
import unittest, strutils, tables, times, math
import unittest, strutils, tables, times, math, options

type
MyTuple = tuple
Expand Down Expand Up @@ -270,6 +270,19 @@ suite "Serialization":
var output = dump(input, tsNone, asTidy, blockOnly)
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]":
let input = "23: dreiundzwanzig\n42: zweiundvierzig"
var result: Table[int32, string]
Expand Down
18 changes: 9 additions & 9 deletions yaml/parser.nim
Original file line number Diff line number Diff line change
Expand Up @@ -358,14 +358,14 @@ template capitalize(s: string): string =
when declared(strutils.capitalizeAscii): strutils.capitalizeAscii(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:
##
## proc name(s: YamlStream, e: var YamlStreamEvent):
## bool {.raises: [YamlParserError].}
result = newStmtList()
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"),
ident("YamlStream")), newIdentDefs(ident("e"), newNimNode(nnkVarTy).add(
ident("YamlStreamEvent")))], newEmptyNode()))
Expand All @@ -378,29 +378,29 @@ proc processStateAsgns(source, target: NimNode) {.compileTime.} =
## `state = [name]` with the appropriate code for changing states.
for child in source.children:
if child.kind == nnkAsgn and child[0].kind == nnkIdent:
if $child[0].ident == "state":
if child[0].strVal == "state":
assert child[1].kind == nnkIdent
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"))
else:
newNameId =
newIdentNode("state" & capitalize($child[1].ident))
newIdentNode("state" & capitalize(child[1].strVal))
target.add(newAssignment(newDotExpr(
newIdentNode("s"), newIdentNode("nextImpl")), newNameId))
continue
elif $child[0].ident == "stored":
elif child[0].strVal == "stored":
assert child[1].kind == nnkIdent
let newNameId =
newIdentNode("state" & capitalize($child[1].ident))
newIdentNode("state" & capitalize(child[1].strVal))
target.add(newAssignment(newDotExpr(newIdentNode("c"),
newIdentNode("storedState")), newNameId))
continue
var processed = copyNimNode(child)
processStateAsgns(child, 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
##
## proc(s: YamlStream, e: var YamlStreamEvent):
Expand All @@ -413,7 +413,7 @@ macro parserState(name: untyped, impl: untyped): typed =
## `c`. You can change the parser state by a assignment `state = [newState]`.
## The [newState] must have been declared with states(...) previously.
let
nameStr = $name.ident
nameStr = name.strVal
nameId = newIdentNode("state" & capitalize(nameStr))
var procImpl = quote do:
debug("state: " & `nameStr`)
Expand Down
49 changes: 36 additions & 13 deletions yaml/serialization.nim
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
## type. Please consult the serialization guide on the NimYAML website for more
## 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
export stream
# *something* in here needs externally visible `==`(x,y: AnchorId),
Expand Down Expand Up @@ -708,7 +708,7 @@ proc ifNotTransient(tSym: NimNode, fieldIndex: int, content: openarray[NimNode],
))

macro ensureAllFieldsPresent(s: YamlStream, t: typedesc, tIndex: int, o: typed,
matched: typed): typed =
matched: typed) =
let
dbp = defaultBitvectorProc
defaultValues = genSym(nskConst, "defaultValues")
Expand Down Expand Up @@ -751,7 +751,7 @@ macro ensureAllFieldsPresent(s: YamlStream, t: typedesc, tIndex: int, o: typed,
[checkMissing(s, t, tName, child, field, matched, o, defaultValues)]))
inc(field)

macro fetchTransientIndex(t: typedesc, tIndex: untyped): typed =
macro fetchTransientIndex(t: typedesc, tIndex: untyped) =
quote do:
when compiles(`transientBitvectorProc`(`t`)):
const `tIndex` = `transientBitvectorProc`(`t`)
Expand All @@ -760,7 +760,7 @@ macro fetchTransientIndex(t: typedesc, tIndex: untyped): typed =

macro constructFieldValue(t: typedesc, tIndex: int, stream: untyped,
context: untyped, name: untyped, o: untyped,
matched: untyped, failOnUnknown: bool): typed =
matched: untyped, failOnUnknown: bool) =
let
tDecl = getType(t)
tName = $tDecl[1]
Expand Down Expand Up @@ -855,14 +855,14 @@ proc isVariantObject(t: NimNode): bool {.compileTime.} =
if child.kind == nnkRecCase: return true
return false

macro injectIgnoredKeyList(t: typedesc, ident: untyped): typed =
macro injectIgnoredKeyList(t: typedesc, ident: untyped) =
result = quote do:
when compiles(`ignoredKeyListProc`(`t`)):
const `ident` = ignoredKeyLists[`ignoredKeyListproc`(`t`)]
else:
const `ident` = newSeq[string]()

macro injectFailOnUnknownKeys(t: typedesc, ident: untyped): typed =
macro injectFailOnUnknownKeys(t: typedesc, ident: untyped) =
result = quote do:
when compiles(`ignoreUnknownKeysProc`(`t`)):
const `ident` = false
Expand Down Expand Up @@ -948,7 +948,7 @@ proc constructObject*[O: object|tuple](
## Overridable default implementation for custom object and tuple types
constructObjectDefault(s, c, result)

macro genRepresentObject(t: typedesc, value, childTagStyle: typed): typed =
macro genRepresentObject(t: typedesc, value, childTagStyle: typed) =
result = newStmtList()
let tSym = genSym(nskConst, ":tSym")
result.add(quote do:
Expand Down Expand Up @@ -1068,7 +1068,7 @@ proc representObject*[O: enum](value: O, ts: TagStyle,
proc yamlTag*[O](T: typedesc[ref O]): TagId {.inline, raises: [].} = yamlTag(O)

macro constructImplicitVariantObject(s, c, r, possibleTagIds: untyped,
t: typedesc): typed =
t: typedesc) =
let tDesc = getType(getType(t)[1])
yAssert tDesc.kind == nnkObjectTy
let recCase = tDesc[2][0]
Expand Down Expand Up @@ -1198,6 +1198,19 @@ proc constructChild*[T](s: var YamlStream, c: ConstructionContext,
raise s.constructionError("Anchor on non-ref type")
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):
# in JS, Time is a ref type. Therefore, we need this specialization so that
# it is not handled by the general ref-type handler.
Expand Down Expand Up @@ -1323,6 +1336,16 @@ proc representChild*[O](value: ref O, ts: TagStyle, c: SerializationContext) =
c.put(ex)
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,
c: SerializationContext) =
when isImplicitVariantObject(value):
Expand Down Expand Up @@ -1466,7 +1489,7 @@ macro setImplicitVariantObjectMarker(t: typedesc): untyped =
{.fatal: "Cannot mark object with transient fields as implicit".}
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
## of nothing but a case expression and each branch of the case expression
## containing exactly one field - with the exception that one branch may
Expand Down Expand Up @@ -1525,7 +1548,7 @@ proc fieldIdent(field: NimNode): NimNode {.compileTime.} =
raise newException(Exception, "invalid node type (expected ident):" &
$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
## 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
Expand Down Expand Up @@ -1556,7 +1579,7 @@ macro markAsTransient*(t: typedesc, field: untyped): typed =
transientVectors[`transientBitvectorProc`(`t`)].incl(
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
## 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
Expand Down Expand Up @@ -1590,7 +1613,7 @@ macro setDefaultValue*(t: typedesc, field: untyped, value: typed): typed =
`defaultValueGetter`(`t`).`fieldName` = `value`
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
## 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``,
Expand All @@ -1613,7 +1636,7 @@ macro ignoreInputKey*(t: typedesc, name: string{lit}): typed =
static:
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
## mapping key that does not map to an existing field inside the object or
## tuple shall be ignored.
Expand Down
4 changes: 2 additions & 2 deletions yaml/taglib.nim
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ var
## registered URIs here to be able to generate a static compiler error
## 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
## when loading and dumping values of this type.
when uri in registeredUris:
Expand All @@ -239,7 +239,7 @@ template setTagUri*(t: typedesc, uri: string): typed =
proc yamlTag*(T: typedesc[t]): TagId {.inline, raises: [].} = id
## 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
## you choose a symbol for the `TagId <#TagId>`_ of the uri. This is only
## necessary if you want to implement serialization / construction yourself.
Expand Down

0 comments on commit 1dfc2a3

Please sign in to comment.