NimYAML/private/serialization.nim

880 lines
34 KiB
Nim

# NimYAML - YAML implementation in Nim
# (c) Copyright 2015 Felix Krause
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
proc newConstructionContext*(): ConstructionContext =
new(result)
result.refs = initTable[AnchorId, pointer]()
proc newSerializationContext*(s: AnchorStyle): SerializationContext =
new(result)
result.refs = initTable[pointer, AnchorId]()
result.style = s
result.nextAnchorId = 0.AnchorId
template presentTag*(t: typedesc, ts: TagStyle): TagId =
## Get the TagId that represents the given type in the given style
if ts == tsNone: yTagQuestionMark else: yamlTag(t)
proc lazyLoadTag(uri: string): TagId {.inline, raises: [].} =
try: result = serializationTagLibrary.tags[uri]
except KeyError: result = serializationTagLibrary.registerUri(uri)
proc safeTagUri(id: TagId): string {.raises: [].} =
try:
let uri = serializationTagLibrary.uri(id)
if uri.len > 0 and uri[0] == '!': return uri[1..uri.len - 1]
else: return uri
except KeyError: internalError("Unexpected KeyError for TagId " & $id)
template constructScalarItem*(s: var YamlStream, i: expr,
t: typedesc, content: untyped) =
## Helper template for implementing ``constructObject`` for types that
## are constructed from a scalar. ``i`` is the identifier that holds
## the scalar as ``YamlStreamEvent`` in the content. Exceptions raised in
## the content will be automatically catched and wrapped in
## ``YamlConstructionError``, which will then be raised.
let i = s.next()
if i.kind != yamlScalar:
raise newException(YamlConstructionError, "Expected scalar")
try: content
except YamlConstructionError: raise
except Exception:
var e = newException(YamlConstructionError,
"Cannot construct to " & name(t) & ": " & item.scalarContent)
e.parent = getCurrentException()
raise e
proc yamlTag*(T: typedesc[string]): TagId {.inline, noSideEffect, raises: [].} =
yTagString
proc constructObject*(s: var YamlStream, c: ConstructionContext,
result: var string)
{.raises: [YamlConstructionError, YamlStreamError].} =
## costructs a string from a YAML scalar
constructScalarItem(s, item, string):
result = item.scalarContent
proc representObject*(value: string, ts: TagStyle,
c: SerializationContext, tag: TagId): RawYamlStream {.raises: [].} =
## represents a string as YAML scalar
result = iterator(): YamlStreamEvent =
yield scalarEvent(value, tag, yAnchorNone)
proc constructObject*[T: int8|int16|int32|int64](
s: var YamlStream, c: ConstructionContext, result: var T)
{.raises: [YamlConstructionError, YamlStreamError].} =
## constructs an integer value from a YAML scalar
constructScalarItem(s, item, T):
result = T(parseBiggestInt(item.scalarContent))
proc constructObject*(s: var YamlStream, c: ConstructionContext,
result: var int)
{.raises: [YamlConstructionError, YamlStreamError], inline.} =
## constructs an integer of architecture-defined length by loading it into
## int32 and then converting it.
var i32Result: int32
constructObject(s, c, i32Result)
result = int(i32Result)
proc representObject*[T: int8|int16|int32|int64](value: T, ts: TagStyle,
c: SerializationContext, tag: TagId): RawYamlStream {.raises: [].} =
## represents an integer value as YAML scalar
result = iterator(): YamlStreamEvent =
yield scalarEvent($value, tag, yAnchorNone)
proc representObject*(value: int, tagStyle: TagStyle,
c: SerializationContext, tag: TagId): RawYamlStream
{.raises: [], inline.}=
## represent an integer of architecture-defined length by casting it to int32.
## on 64-bit systems, this may cause a RangeError.
# currently, sizeof(int) is at least sizeof(int32).
result = iterator(): YamlStreamEvent =
var ev: YamlStreamEvent
try: ev = scalarEvent($int32(value), tag, yAnchorNone)
except RangeError:
var e = newException(YamlStreamError, getCurrentExceptionMsg())
e.parent = getCurrentException()
raise e
yield ev
{.push overflowChecks: on.}
proc parseBiggestUInt(s: string): uint64 =
result = 0
for c in s:
if c in {'0'..'9'}: result *= 10.uint64 + (uint64(c) - uint64('0'))
elif c == '_': discard
else: raise newException(ValueError, "Invalid char in uint: " & c)
{.pop.}
proc constructObject*[T: uint8|uint16|uint32|uint64](
s: var YamlStream, c: ConstructionContext, result: var T)
{.raises: [YamlConstructionError, YamlStreamError].} =
## construct an unsigned integer value from a YAML scalar
constructScalarItem(s, item, T):
result = T(parseBiggestUInt(item.scalarContent))
proc constructObject*(s: var YamlStream, c: ConstructionContext,
result: var uint)
{.raises: [YamlConstructionError, YamlStreamError], inline.} =
## represent an unsigned integer of architecture-defined length by loading it
## into uint32 and then converting it.
var u32Result: uint32
constructObject(s, c, u32Result)
result= uint(u32Result)
proc representObject*[T: uint8|uint16|uint32|uint64](value: T, ts: TagStyle,
c: SerializationContext, tag: TagId): RawYamlStream {.raises: [].} =
## represents an unsigned integer value as YAML scalar
result = iterator(): YamlStreamEvent =
yield scalarEvent($value, tag, yAnchorNone)
proc representObject*(value: uint, ts: TagStyle, c: SerializationContext,
tag: TagId): RawYamlStream {.raises: [], inline.} =
## represent an unsigned integer of architecture-defined length by casting it
## to int32. on 64-bit systems, this may cause a RangeError.
result = iterator(): YamlStreamEvent =
var ev: YamlStreamEvent
try: ev = scalarEvent($uint32(value), tag, yAnchorNone)
except RangeError:
var e = newException(YamlStreamError, getCurrentExceptionMsg())
e.parent = getCurrentException()
raise e
yield ev
proc constructObject*[T: float|float32|float64](
s: var YamlStream, c: ConstructionContext, result: var T)
{.raises: [YamlConstructionError, YamlStreamError].} =
## construct a float value from a YAML scalar
constructScalarItem(s, item, T):
let hint = guessType(item.scalarContent)
case hint
of yTypeFloat:
discard parseBiggestFloat(item.scalarContent, result)
of yTypeInteger:
discard parseBiggestFloat(item.scalarContent, result)
of yTypeFloatInf:
if item.scalarContent[0] == '-': result = NegInf
else: result = Inf
of yTypeFloatNaN: result = NaN
else:
raise newException(YamlConstructionError,
"Cannot construct to float: " & item.scalarContent)
proc representObject*[T: float|float32|float64](value: T, ts: TagStyle,
c: SerializationContext, tag: TagId): RawYamlStream {.raises: [].} =
## represents a float value as YAML scalar
result = iterator(): YamlStreamEvent =
var asString: string
case value
of Inf: asString = ".inf"
of NegInf: asString = "-.inf"
of NaN: asString = ".nan"
else: asString = $value
yield scalarEvent(asString, tag, yAnchorNone)
proc yamlTag*(T: typedesc[bool]): TagId {.inline, raises: [].} = yTagBoolean
proc constructObject*(s: var YamlStream, c: ConstructionContext,
result: var bool)
{.raises: [YamlConstructionError, YamlStreamError].} =
## constructs a bool value from a YAML scalar
constructScalarItem(s, item, bool):
case guessType(item.scalarContent)
of yTypeBoolTrue: result = true
of yTypeBoolFalse: result = false
else:
raise newException(YamlConstructionError,
"Cannot construct to bool: " & item.scalarContent)
proc representObject*(value: bool, ts: TagStyle, c: SerializationContext,
tag: TagId): RawYamlStream {.raises: [].} =
## represents a bool value as a YAML scalar
result = iterator(): YamlStreamEvent =
yield scalarEvent(if value: "y" else: "n", tag, yAnchorNone)
proc constructObject*(s: var YamlStream, c: ConstructionContext,
result: var char)
{.raises: [YamlConstructionError, YamlStreamError].} =
## constructs a char value from a YAML scalar
constructScalarItem(s, item, char):
if item.scalarContent.len != 1:
raise newException(YamlConstructionError,
"Cannot construct to char (length != 1): " & item.scalarContent)
else: result = item.scalarContent[0]
proc representObject*(value: char, ts: TagStyle, c: SerializationContext,
tag: TagId): RawYamlStream {.raises: [].} =
## represents a char value as YAML scalar
result = iterator(): YamlStreamEvent =
yield scalarEvent("" & value, tag, yAnchorNone)
proc yamlTag*[I](T: typedesc[seq[I]]): TagId {.inline, raises: [].} =
let uri = "!nim:system:seq(" & safeTagUri(yamlTag(I)) & ')'
result = lazyLoadTag(uri)
proc yamlTag*[I](T: typedesc[set[I]]): TagId {.inline, raises: [].} =
let uri = "!nim:system:set(" & safeTagUri(yamlTag(I)) & ')'
result = lazyLoadTag(uri)
proc constructObject*[T](s: var YamlStream, c: ConstructionContext,
result: var seq[T])
{.raises: [YamlConstructionError, YamlStreamError].} =
## constructs a Nim seq from a YAML sequence
let event = s.next()
if event.kind != yamlStartSeq:
raise newException(YamlConstructionError, "Expected sequence start")
result = newSeq[T]()
while s.peek().kind != yamlEndSeq:
var item: T
constructChild(s, c, item)
result.add(item)
discard s.next()
proc constructObject*[T](s: var YamlStream, c: ConstructionContext,
result: var set[T])
{.raises: [YamlConstructionError, YamlStreamError].} =
## constructs a Nim seq from a YAML sequence
let event = s.next()
if event.kind != yamlStartSeq:
raise newException(YamlConstructionError, "Expected sequence start")
result = {}
while s.peek().kind != yamlEndSeq:
var item: T
constructChild(s, c, item)
result.incl(item)
discard s.next()
proc representObject*[T](value: seq[T]|set[T], ts: TagStyle,
c: SerializationContext, tag: TagId): RawYamlStream {.raises: [].} =
## represents a Nim seq as YAML sequence
result = iterator(): YamlStreamEvent =
let childTagStyle = if ts == tsRootOnly: tsNone else: ts
yield startSeqEvent(tag)
for item in value:
var events = representChild(item, childTagStyle, c)
while true:
let event = events()
if finished(events): break
yield event
yield endSeqEvent()
proc yamlTag*[I, V](T: typedesc[array[I, V]]): TagId {.inline, raises: [].} =
const rangeName = name(I)
let uri = "!nim:system:array(" & rangeName[6..rangeName.high()] & "," &
safeTagUri(yamlTag(V)) & ')'
result = lazyLoadTag(uri)
proc constructObject*[I, T](s: var YamlStream, c: ConstructionContext,
result: var array[I, T])
{.raises: [YamlConstructionError, YamlStreamError].} =
## constructs a Nim array from a YAML sequence
var event = s.next()
if event.kind != yamlStartSeq:
raise newException(YamlConstructionError, "Expected sequence start")
for index in low(I)..high(I):
event = s.peek()
if event.kind == yamlEndSeq:
raise newException(YamlConstructionError, "Too few array values")
constructChild(s, c, result[index])
event = s.next()
if event.kind != yamlEndSeq:
raise newException(YamlConstructionError, "Too much array values")
proc representObject*[I, T](value: array[I, T], ts: TagStyle,
c: SerializationContext, tag: TagId): RawYamlStream {.raises: [].} =
## represents a Nim array as YAML sequence
result = iterator(): YamlStreamEvent =
let childTagStyle = if ts == tsRootOnly: tsNone else: ts
yield startSeqEvent(tag)
for item in value:
var events = representChild(item, childTagStyle, c)
while true:
let event = events()
if finished(events): break
yield event
yield endSeqEvent()
proc yamlTag*[K, V](T: typedesc[Table[K, V]]): TagId {.inline, raises: [].} =
try:
let uri = "!nim:tables:Table(" & safeTagUri(yamlTag(K)) & "," &
safeTagUri(yamlTag(V)) & ")"
result = lazyLoadTag(uri)
except KeyError:
# cannot happen (theoretically, you know)
internalError("Unexpected KeyError")
proc constructObject*[K, V](s: var YamlStream, c: ConstructionContext,
result: var Table[K, V])
{.raises: [YamlConstructionError, YamlStreamError].} =
## constructs a Nim Table from a YAML mapping
let event = s.next()
if event.kind != yamlStartMap:
raise newException(YamlConstructionError, "Expected map start, got " &
$event.kind)
result = initTable[K, V]()
while s.peek.kind != yamlEndMap:
var
key: K
value: V
constructChild(s, c, key)
constructChild(s, c, value)
if result.contains(key):
raise newException(YamlConstructionError, "Duplicate table key!")
result[key] = value
discard s.next()
proc representObject*[K, V](value: Table[K, V], ts: TagStyle,
c: SerializationContext, tag: TagId): RawYamlStream {.raises:[].} =
## represents a Nim Table as YAML mapping
result = iterator(): YamlStreamEvent =
let childTagStyle = if ts == tsRootOnly: tsNone else: ts
yield startMapEvent(tag)
for key, value in value.pairs:
var events = representChild(key, childTagStyle, c)
while true:
let event = events()
if finished(events): break
yield event
events = representChild(value, childTagStyle, c)
while true:
let event = events()
if finished(events): break
yield event
yield endMapEvent()
proc yamlTag*[K, V](T: typedesc[OrderedTable[K, V]]): TagId
{.inline, raises: [].} =
try:
let uri = "!nim:tables:OrderedTable(" & safeTagUri(yamlTag(K)) & "," &
safeTagUri(yamlTag(V)) & ")"
result = lazyLoadTag(uri)
except KeyError:
# cannot happen (theoretically, you know)
internalError("Unexpected KeyError")
proc constructObject*[K, V](s: var YamlStream, c: ConstructionContext,
result: var OrderedTable[K, V])
{.raises: [YamlConstructionError, YamlStreamError].} =
## constructs a Nim OrderedTable from a YAML mapping
let event = s.next()
if event.kind != yamlStartSeq:
raise newException(YamlConstructionError, "Expected seq start, got " &
$event.kind)
result = initOrderedTable[K, V]()
while s.peek.kind != yamlEndSeq:
var
key: K
value: V
if s.next().kind != yamlStartMap:
raise newException(YamlConstructionError,
"Expected map start, got " & $event.kind)
constructChild(s, c, key)
constructChild(s, c, value)
if s.next().kind != yamlEndMap:
raise newException(YamlConstructionError,
"Expected map end, got " & $event.kind)
if result.contains(key):
raise newException(YamlConstructionError, "Duplicate table key!")
result.add(key, value)
discard s.next()
proc representObject*[K, V](value: OrderedTable[K, V], ts: TagStyle,
c: SerializationContext, tag: TagId): RawYamlStream {.raises: [].} =
result = iterator(): YamlStreamEvent =
let childTagStyle = if ts == tsRootOnly: tsNone else: ts
yield startSeqEvent(tag)
for key, value in value.pairs:
yield startMapEvent()
var events = representChild(key, childTagStyle, c)
while true:
let event = events()
if finished(events): break
yield event
events = representChild(value, childTagStyle, c)
while true:
let event = events()
if finished(events): break
yield event
yield endMapEvent()
yield endSeqEvent()
proc yamlTag*(T: typedesc[object|enum]):
TagId {.inline, raises: [].} =
var uri = "!nim:custom:" & (typetraits.name(type(T)))
try: serializationTagLibrary.tags[uri]
except KeyError: serializationTagLibrary.registerUri(uri)
proc yamlTag*(T: typedesc[tuple]):
TagId {.inline, raises: [].} =
var
i: T
uri = "!nim:tuple("
first = true
for name, value in fieldPairs(i):
if first: first = false
else: uri.add(",")
uri.add(safeTagUri(yamlTag(type(value))))
uri.add(")")
try: serializationTagLibrary.tags[uri]
except KeyError: serializationTagLibrary.registerUri(uri)
macro constructFieldValue(t: typedesc, stream: expr, context: expr,
name: expr, o: expr): stmt =
let tDesc = getType(getType(t)[1])
result = newNimNode(nnkCaseStmt).add(name)
for child in tDesc[2].children:
if child.kind == nnkRecCase:
let
discriminant = newDotExpr(o, newIdentNode($child[0]))
discType = newCall("type", discriminant)
var disOb = newNimNode(nnkOfBranch).add(newStrLitNode($child[0]))
disOb.add(newStmtList(
newNimNode(nnkVarSection).add(
newNimNode(nnkIdentDefs).add(
newIdentNode("value"), discType, newEmptyNode())),
newCall("constructChild", stream, context, newIdentNode("value")),
newAssignment(discriminant, newIdentNode("value"))))
result.add(disOb)
for bIndex in 1 .. len(child) - 1:
let discTest = infix(discriminant, "==", child[bIndex][0])
for item in child[bIndex][1].children:
yAssert item.kind == nnkSym
var ob = newNimNode(nnkOfBranch).add(newStrLitNode($item))
let field = newDotExpr(o, newIdentNode($item))
var ifStmt = newIfStmt((cond: discTest, body: newStmtList(
newCall("constructChild", stream, context, field))))
ifStmt.add(newNimNode(nnkElse).add(newNimNode(nnkRaiseStmt).add(
newCall("newException", newIdentNode("YamlConstructionError"),
infix(newStrLitNode("Field " & $item & " not allowed for " &
$child[0] & " == "), "&", prefix(discriminant, "$"))))))
ob.add(newStmtList(ifStmt))
result.add(ob)
else:
yAssert child.kind == nnkSym
var ob = newNimNode(nnkOfBranch).add(newStrLitNode($child))
let field = newDotExpr(o, newIdentNode($child))
ob.add(newStmtList(newCall("constructChild", stream, context, field)))
result.add(ob)
proc isVariantObject(t: typedesc): bool {.compileTime.} =
let tDesc = getType(t)
if tDesc.kind != nnkObjectTy: return false
for child in tDesc[2].children:
if child.kind == nnkRecCase: return true
return false
proc constructObject*[O: object|tuple](
s: var YamlStream, c: ConstructionContext, result: var O)
{.raises: [YamlConstructionError, YamlStreamError].} =
## constructs a Nim object or tuple from a YAML mapping
let e = s.next()
const
startKind = when isVariantObject(O): yamlStartSeq else: yamlStartMap
endKind = when isVariantObject(O): yamlEndSeq else: yamlEndMap
if e.kind != startKind:
raise newException(YamlConstructionError, "While constructing " &
typetraits.name(O) & ": Expected map start, got " & $e.kind)
when isVariantObject(O): reset(result) # make discriminants writeable
while s.peek.kind != endKind:
# todo: check for duplicates in input and raise appropriate exception
# also todo: check for missing items and raise appropriate exception
var e = s.next()
when isVariantObject(O):
if e.kind != yamlStartMap:
raise newException(YamlConstructionError,
"Expected single-pair map, got " & $e.kind)
e = s.next()
if e.kind != yamlScalar:
raise newException(YamlConstructionError,
"Expected field name, got " & $e.kind)
let name = e.scalarContent
when result is tuple:
for fname, value in fieldPairs(result):
if fname == name:
constructChild(s, c, value)
break
else:
constructFieldValue(O, s, c, name, result)
when isVariantObject(O):
e = s.next()
if e.kind != yamlEndMap:
raise newException(YamlConstructionError,
"Expected end of single-pair map, got " & $e.kind)
discard s.next()
proc representObject*[O: object|tuple](value: O, ts: TagStyle,
c: SerializationContext, tag: TagId): RawYamlStream {.raises: [].} =
## represents a Nim object or tuple as YAML mapping
result = iterator(): YamlStreamEvent =
let childTagStyle = if ts == tsRootOnly: tsNone else: ts
when isVariantObject(O):
yield startSeqEvent(tag, yAnchorNone)
else:
yield startMapEvent(tag, yAnchorNone)
for name, value in fieldPairs(value):
when isVariantObject(O):
yield startMapEvent(yTagQuestionMark, yAnchorNone)
yield scalarEvent(name,
if childTagStyle == tsNone: yTagQuestionMark else:
yTagNimField, yAnchorNone)
var events = representChild(value, childTagStyle, c)
while true:
let event = events()
if finished(events): break
yield event
when isVariantObject(O):
yield endMapEvent()
when isVariantObject(O):
yield endSeqEvent()
else:
yield endMapEvent()
proc constructObject*[O: enum](s: var YamlStream, c: ConstructionContext,
result: var O)
{.raises: [YamlConstructionError, YamlStreamError].} =
## constructs a Nim enum from a YAML scalar
let e = s.next()
if e.kind != yamlScalar:
raise newException(YamlConstructionError, "Expected scalar, got " &
$e.kind)
try: result = parseEnum[O](e.scalarContent)
except ValueError:
var ex = newException(YamlConstructionError, "Cannot parse '" &
e.scalarContent & "' as " & type(O).name)
ex.parent = getCurrentException()
raise ex
proc representObject*[O: enum](value: O, ts: TagStyle,
c: SerializationContext, tag: TagId): RawYamlStream {.raises: [].} =
## represents a Nim enum as YAML scalar
result = iterator(): YamlStreamEvent =
yield scalarEvent($value, tag, yAnchorNone)
proc yamlTag*[O](T: typedesc[ref O]): TagId {.inline, raises: [].} = yamlTag(O)
macro constructImplicitVariantObject(s, c, r, possibleTagIds: expr,
t: typedesc): stmt =
let tDesc = getType(getType(t)[1])
yAssert tDesc.kind == nnkObjectTy
let recCase = tDesc[2][0]
yAssert recCase.kind == nnkRecCase
let
discriminant = newDotExpr(r, newIdentNode($recCase[0]))
discType = newCall("type", discriminant)
var ifStmt = newNimNode(nnkIfStmt)
for i in 1 .. recCase.len - 1:
yAssert recCase[i].kind == nnkOfBranch
var branch = newNimNode(nnkElifBranch)
var branchContent = newStmtList(newAssignment(discriminant, recCase[i][0]))
case recCase[i][1].len
of 0:
branch.add(infix(newIdentNode("yTagNull"), "in", possibleTagIds))
branchContent.add(newNimNode(nnkDiscardStmt).add(newCall("next", s)))
of 1:
let field = newDotExpr(r, newIdentNode($recCase[i][1][0]))
branch.add(infix(
newCall("yamlTag", newCall("type", field)), "in", possibleTagIds))
branchContent.add(newCall("constructChild", s, c, field))
else: internalError("Too many children: " & $recCase[i][1].len)
branch.add(branchContent)
ifStmt.add(branch)
let raiseStmt = newNimNode(nnkRaiseStmt).add(
newCall("newException", newIdentNode("YamlConstructionError"),
infix(newStrLitNode("This value type does not map to any field in " &
typetraits.name(t) & ": "), "&",
newCall("uri", newIdentNode("serializationTagLibrary"),
newNimNode(nnkBracketExpr).add(possibleTagIds, newIntLitNode(0)))
)
))
ifStmt.add(newNimNode(nnkElse).add(newNimNode(nnkTryStmt).add(
newStmtList(raiseStmt), newNimNode(nnkExceptBranch).add(
newIdentNode("KeyError"), newStmtList(newCall("internalError",
newStrLitNode("Unexcpected KeyError")))
))))
result = newStmtList(newCall("reset", r), ifStmt)
proc constructChild*[T](s: var YamlStream, c: ConstructionContext,
result: var T) =
let item = s.peek()
when compiles(implicitVariantObject(result)):
var possibleTagIds = newSeq[TagId]()
case item.kind
of yamlScalar:
case item.scalarTag
of yTagQuestionMark:
case guessType(item.scalarContent)
of yTypeInteger:
possibleTagIds.add([yamlTag(int), yamlTag(int8), yamlTag(int16),
yamlTag(int32), yamlTag(int64)])
if item.scalarContent[0] != '-':
possibleTagIds.add([yamlTag(uint), yamlTag(uint8), yamlTag(uint16),
yamlTag(uint32), yamlTag(uint64)])
of yTypeFloat, yTypeFloatInf, yTypeFloatNaN:
possibleTagIds.add([yamlTag(float), yamlTag(float32),
yamlTag(float64)])
of yTypeBoolTrue, yTypeBoolFalse:
possibleTagIds.add(yamlTag(bool))
of yTypeNull:
raise newException(YamlConstructionError, "not implemented!")
of yTypeUnknown:
possibleTagIds.add(yamlTag(string))
of yTagExclamationMark:
possibleTagIds.add(yamlTag(string))
else:
possibleTagIds.add(item.scalarTag)
of yamlStartMap:
if item.mapTag in [yTagQuestionMark, yTagExclamationMark]:
raise newException(YamlConstructionError,
"Complex value of implicit variant object type must have a tag.")
possibleTagIds.add(item.mapTag)
of yamlStartSeq:
if item.seqTag in [yTagQuestionMark, yTagExclamationMark]:
raise newException(YamlConstructionError,
"Complex value of implicit variant object type must have a tag.")
possibleTagIds.add(item.seqTag)
else: internalError("Unexpected item kind: " & $item.kind)
constructImplicitVariantObject(s, c, result, possibleTagIds, T)
else:
case item.kind
of yamlScalar:
if item.scalarTag notin [yTagQuestionMark, yTagExclamationMark,
yamlTag(T)]:
raise newException(YamlConstructionError, "Wrong tag for " &
typetraits.name(T))
elif item.scalarAnchor != yAnchorNone:
raise newException(YamlConstructionError, "Anchor on non-ref type")
of yamlStartMap:
if item.mapTag notin [yTagQuestionMark, yamlTag(T)]:
raise newException(YamlConstructionError, "Wrong tag for " &
typetraits.name(T))
elif item.mapAnchor != yAnchorNone:
raise newException(YamlConstructionError, "Anchor on non-ref type")
of yamlStartSeq:
if item.seqTag notin [yTagQuestionMark, yamlTag(T)]:
raise newException(YamlConstructionError, "Wrong tag for " &
typetraits.name(T))
elif item.seqAnchor != yAnchorNone:
raise newException(YamlConstructionError, "Anchor on non-ref type")
else: internalError("Unexpected item kind: " & $item.kind)
constructObject(s, c, result)
proc constructChild*(s: var YamlStream, c: ConstructionContext,
result: var string) =
let item = s.peek()
if item.kind == yamlScalar:
if item.scalarTag == yTagNimNilString:
discard s.next()
result = nil
return
elif item.scalarTag notin
[yTagQuestionMark, yTagExclamationMark, yamlTag(string)]:
raise newException(YamlConstructionError, "Wrong tag for string")
elif item.scalarAnchor != yAnchorNone:
raise newException(YamlConstructionError, "Anchor on non-ref type")
constructObject(s, c, result)
proc constructChild*[T](s: var YamlStream, c: ConstructionContext,
result: var seq[T]) =
let item = s.peek()
if item.kind == yamlScalar:
if item.scalarTag == yTagNimNilSeq:
discard s.next()
result = nil
return
elif item.kind == yamlStartSeq:
if item.seqTag notin [yTagQuestionMark, yamlTag(seq[T])]:
raise newException(YamlConstructionError, "Wrong tag for " &
typetraits.name(seq[T]))
elif item.seqAnchor != yAnchorNone:
raise newException(YamlConstructionError, "Anchor on non-ref type")
constructObject(s, c, result)
proc constructChild*[O](s: var YamlStream, c: ConstructionContext,
result: var ref O) =
var e = s.peek()
if e.kind == yamlScalar:
if e.scalarTag == yTagNull or (e.scalarTag == yTagQuestionMark and
guessType(e.scalarContent) == yTypeNull):
result = nil
discard s.next()
return
elif e.kind == yamlAlias:
result = cast[ref O](c.refs.getOrDefault(e.aliasTarget))
discard s.next()
return
new(result)
template removeAnchor(anchor: var AnchorId) {.dirty.} =
if anchor != yAnchorNone:
yAssert(not c.refs.hasKey(anchor))
c.refs[anchor] = cast[pointer](result)
anchor = yAnchorNone
case e.kind
of yamlScalar: removeAnchor(e.scalarAnchor)
of yamlStartMap: removeAnchor(e.mapAnchor)
of yamlStartSeq: removeAnchor(e.seqAnchor)
else: internalError("Unexpected event kind: " & $e.kind)
s.peek = e
try: constructChild(s, c, result[])
except YamlConstructionError, YamlStreamError: raise
except Exception:
var e = newException(YamlStreamError, getCurrentExceptionMsg())
e.parent = getCurrentException()
raise e
proc representChild*(value: string, ts: TagStyle, c: SerializationContext):
RawYamlStream =
if isNil(value):
result = iterator(): YamlStreamEvent =
yield scalarEvent("", yTagNimNilString)
else: result = representObject(value, ts, c, presentTag(string, ts))
proc representChild*[T](value: seq[T], ts: TagStyle, c: SerializationContext):
RawYamlStream =
if isNil(value):
result = iterator(): YamlStreamEvent =
yield scalarEvent("", yTagNimNilSeq)
else: result = representObject(value, ts, c, presentTag(seq[T], ts))
proc representChild*[O](value: ref O, ts: TagStyle, c: SerializationContext):
RawYamlStream =
if isNil(value):
result = iterator(): YamlStreamEvent =
yield scalarEvent("~", yTagNull)
elif c.style == asNone: result = representChild(value[], ts, c)
else:
let p = cast[pointer](value)
if c.refs.hasKey(p):
if c.refs.getOrDefault(p) == yAnchorNone:
c.refs[p] = c.nextAnchorId
c.nextAnchorId = AnchorId(int(c.nextAnchorId) + 1)
result = iterator(): YamlStreamEvent {.raises: [].} =
yield aliasEvent(c.refs.getOrDefault(p))
return
if c.style == asAlways:
c.refs[p] = c.nextAnchorId
c.nextAnchorId = AnchorId(int(c.nextAnchorId) + 1)
else: c.refs[p] = yAnchorNone
let
a = if c.style == asAlways: c.refs.getOrDefault(p) else: cast[AnchorId](p)
childTagStyle = if ts == tsAll: tsAll else: tsRootOnly
result = iterator(): YamlStreamEvent =
var child = representChild(value[], childTagStyle, c)
var first = child()
yAssert(not finished(child))
case first.kind
of yamlStartMap:
first.mapAnchor = a
if ts == tsNone: first.mapTag = yTagQuestionMark
of yamlStartSeq:
first.seqAnchor = a
if ts == tsNone: first.seqTag = yTagQuestionMark
of yamlScalar:
first.scalarAnchor = a
if ts == tsNone and guessType(first.scalarContent) != yTypeNull:
first.scalarTag = yTagQuestionMark
else: discard
yield first
while true:
let event = child()
if finished(child): break
yield event
proc representChild*[O](value: O, ts: TagStyle,
c: SerializationContext): RawYamlStream =
when compiles(implicitVariantObject(value)):
# todo: this would probably be nicer if constructed with a macro
var count = 0
for name, field in fieldPairs(value):
if count > 0:
result =
representChild(field, if ts == tsAll: tsAll else: tsRootOnly, c)
inc(count)
if count == 1:
result = iterator(): YamlStreamEvent =
yield scalarEvent("~", yTagNull)
else:
result = representObject(value, ts, c, if ts == tsNone:
yTagQuestionMark else: yamlTag(O))
proc construct*[T](s: var YamlStream, target: var T) =
var context = newConstructionContext()
try:
var e = s.next()
yAssert(e.kind == yamlStartDoc)
constructChild(s, context, target)
e = s.next()
yAssert(e.kind == yamlEndDoc)
except YamlConstructionError:
raise (ref YamlConstructionError)(getCurrentException())
except YamlStreamError:
raise (ref YamlStreamError)(getCurrentException())
except Exception:
# may occur while calling s()
var ex = newException(YamlStreamError, "")
ex.parent = getCurrentException()
raise ex
proc load*[K](input: Stream, target: var K) =
var
parser = newYamlParser(serializationTagLibrary)
events = parser.parse(input)
try: construct(events, target)
except YamlConstructionError:
var e = (ref YamlConstructionError)(getCurrentException())
e.line = parser.getLineNumber()
e.column = parser.getColNumber()
e.lineContent = parser.getLineContent()
raise e
except YamlStreamError:
let e = (ref YamlStreamError)(getCurrentException())
if e.parent of IOError: raise (ref IOError)(e.parent)
elif e.parent of YamlParserError: raise (ref YamlParserError)(e.parent)
else: internalError("Unexpected exception: " & e.parent.repr)
proc setAnchor(a: var AnchorId, q: var Table[pointer, AnchorId])
{.inline.} =
if a != yAnchorNone: a = q.getOrDefault(cast[pointer](a))
proc represent*[T](value: T, ts: TagStyle = tsRootOnly,
a: AnchorStyle = asTidy): YamlStream =
var
context = newSerializationContext(a)
objStream = iterator(): YamlStreamEvent {.raises: [YamlStreamError].} =
yield startDocEvent()
var events = representChild(value, ts, context)
while true:
let e = events()
if finished(events): break
yield e
yield endDocEvent()
if a == asTidy:
var objQueue = newSeq[YamlStreamEvent]()
for event in objStream(): objQueue.add(event)
var backend = iterator(): YamlStreamEvent {.raises: [YamlStreamError].} =
for i in countup(0, objQueue.len - 1):
var event = objQueue[i]
case event.kind
of yamlStartMap: event.mapAnchor.setAnchor(context.refs)
of yamlStartSeq: event.seqAnchor.setAnchor(context.refs)
of yamlScalar: event.scalarAnchor.setAnchor(context.refs)
else: discard
yield event
result = initYamlStream(backend)
else: result = initYamlStream(objStream)
proc dump*[K](value: K, target: Stream, tagStyle: TagStyle = tsRootOnly,
anchorStyle: AnchorStyle = asTidy,
options: PresentationOptions = defaultPresentationOptions) =
var events = represent(value,
if options.style == psCanonical: tsAll else: tagStyle,
if options.style == psJson: asNone else: anchorStyle)
try: present(events, target, serializationTagLibrary, options)
except YamlStreamError:
internalError("Unexpected exception: " & getCurrentException().repr)