From 7bd562e37e0fb4d63013ff215bafd06ef757c0b6 Mon Sep 17 00:00:00 2001 From: Felix Krause Date: Wed, 7 Sep 2022 16:23:50 +0200 Subject: [PATCH] Fixes and improvements to DOM API. * added loadFlattened. Fixes #117 * fixed an error where recursive nodes were not loaded properly into YamlNodes. * made dump with asNone raise an error when a cycle is encountered, instead of running into an endless loop. --- yaml/dom.nim | 31 +++++++++++++++++++++++++++---- yaml/presenter.nim | 4 ++-- yaml/serialization.nim | 40 +++++++++++++++++++++++----------------- 3 files changed, 52 insertions(+), 23 deletions(-) diff --git a/yaml/dom.nim b/yaml/dom.nim index 9ddf178..0bb8ac9 100644 --- a/yaml/dom.nim +++ b/yaml/dom.nim @@ -19,7 +19,7 @@ ## The ``YamlNode`` objects in the DOM can be used similarly to the ``JsonNode`` ## objects of Nim's `json module `_. -import tables, streams, hashes, sets, strutils +import std / [tables, streams, hashes, sets, strutils] import data, stream, taglib, serialization, private/internal, parser, presenter @@ -174,6 +174,7 @@ proc constructChild*(s: var YamlStream, c: ConstructionContext, fields: newTable[YamlNode, YamlNode](), mapStyle: start.mapStyle, startPos: start.startPos, endPos: start.endPos) + addAnchor(c, start.mapProperties.anchor) while s.peek().kind != yamlEndMap: var key: YamlNode = nil @@ -184,28 +185,27 @@ proc constructChild*(s: var YamlStream, c: ConstructionContext, raise newException(YamlConstructionError, "Duplicate key: " & $key) discard s.next() - addAnchor(c, start.mapProperties.anchor) of yamlStartSeq: result = YamlNode(tag: start.seqProperties.tag, kind: ySequence, elems: newSeq[YamlNode](), seqStyle: start.seqStyle, startPos: start.startPos, endPos: start.endPos) + addAnchor(c, start.seqProperties.anchor) while s.peek().kind != yamlEndSeq: var item: YamlNode = nil constructChild(s, c, item) result.elems.add(item) - addAnchor(c, start.seqProperties.anchor) discard s.next() of yamlScalar: result = YamlNode(tag: start.scalarProperties.tag, kind: yScalar, scalarStyle: start.scalarStyle, startPos: start.startPos, endPos: start.endPos) + addAnchor(c, start.scalarProperties.anchor) when defined(gcArc) or defined(gcOrc): result.content = move start.scalarContent else: shallowCopy(result.content, start.scalarContent) - addAnchor(c, start.scalarProperties.anchor) of yamlAlias: result = cast[YamlNode](c.refs.getOrDefault(start.aliasTarget).p) else: internalError("Malformed YamlStream") @@ -342,3 +342,26 @@ iterator mpairs*(node: var YamlNode): ## *yMapping*. Values can be modified. doAssert node.kind == yMapping for key, value in node.fields.mpairs: yield (key, value) + +proc loadFlattened*[K](input: Stream | string, target: var K) + {.raises: [YamlConstructionError, YamlSerializationError, IOError, OSError, + YamlParserError].} = + ## Replaces all aliases with the referenced nodes in the input, then loads + ## the resulting YAML into K. Can be used when anchors & aliases are used like + ## variables in the input, to avoid having to define `ref` types for the + ## anchored data. + var node: YamlNode + load(input, node) + var stream = represent(node, tsNone, asNone) + try: + var e = stream.next() + yAssert(e.kind == yamlStartStream) + construct(stream, target) + e = stream.next() + yAssert(e.kind == yamlEndStream) + except YamlStreamError: + let e = (ref YamlStreamError)(getCurrentException()) + if e.parent of IOError: raise (ref IOError)(e.parent) + if e.parent of OSError: raise (ref OSError)(e.parent) + elif e.parent of YamlParserError: raise (ref YamlParserError)(e.parent) + else: internalError("Unexpected exception: " & $e.parent.name) \ No newline at end of file diff --git a/yaml/presenter.nim b/yaml/presenter.nim index 4bf32e0..892567e 100644 --- a/yaml/presenter.nim +++ b/yaml/presenter.nim @@ -46,10 +46,10 @@ type AnchorStyle* = enum ## How ref object should be serialized. ## - ## - ``asNone``: No anchors will be outputted. Values present at + ## - ``asNone``: No anchors will be written. Values present at ## multiple places in the content that should be serialized will be ## fully serialized at every occurence. If the content is cyclic, this - ## will lead to an endless loop! + ## will raise a YamlSerializationError. ## - ``asTidy``: Anchors will only be generated for objects that ## actually occur more than once in the content to be serialized. ## This is a bit slower and needs more memory than ``asAlways``. diff --git a/yaml/serialization.nim b/yaml/serialization.nim index 40d8a4e..5780769 100644 --- a/yaml/serialization.nim +++ b/yaml/serialization.nim @@ -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, options +import std / [tables, typetraits, strutils, macros, streams, times, parseutils, options] import data, parser, taglib, presenter, stream, private/internal, hints, annotations export data, stream, macros, annotations, options # *something* in here needs externally visible `==`(x,y: AnchorId), @@ -1251,21 +1251,26 @@ proc representChild*[T](value: seq[T], ts: TagStyle, c: SerializationContext) = proc representChild*[O](value: ref O, ts: TagStyle, c: SerializationContext) = if isNil(value): c.put(scalarEvent("~", yTagNull)) - elif c.style == asNone: representChild(value[], ts, c) else: - var val: tuple[a: Anchor, referenced: bool] let p = cast[pointer](value) - if c.refs.hasKey(p): - val = c.refs.getOrDefault(p) - yAssert(val.a != yAnchorNone) - if not val.referenced: - c.refs[p] = (val.a, true) - c.put(aliasEvent(val.a)) - return - if c.style != asNone: - val = (c.nextAnchorId.Anchor, false) - c.refs[p] = val - nextAnchor(c.nextAnchorId, len(c.nextAnchorId) - 1) + # when c.style == asNone, `referenced` is used as indicator that we are + # currently in the process of serializing this node. This enables us to + # detect cycles and raise an error. + var val = c.refs.getOrDefault(p, (c.nextAnchorId.Anchor, c.style == asNone)) + if val.a != c.nextAnchorId.Anchor: + if c.style == asNone: + if val.referenced: + raise newException(YamlSerializationError, + "tried to serialize cyclic graph with asNone") + else: + val = c.refs.getOrDefault(p) + yAssert(val.a != yAnchorNone) + if not val.referenced: + c.refs[p] = (val.a, true) + c.put(aliasEvent(val.a)) + return + c.refs[p] = val + nextAnchor(c.nextAnchorId, len(c.nextAnchorId) - 1) let childTagStyle = if ts == tsAll: tsAll else: tsRootOnly origPut = c.put @@ -1273,19 +1278,20 @@ proc representChild*[O](value: ref O, ts: TagStyle, c: SerializationContext) = var ex = e case ex.kind of yamlStartMap: - ex.mapProperties.anchor = val.a + if c.style != asNone: ex.mapProperties.anchor = val.a if ts == tsNone: ex.mapProperties.tag = yTagQuestionMark of yamlStartSeq: - ex.seqProperties.anchor = val.a + if c.style != asNone: ex.seqProperties.anchor = val.a if ts == tsNone: ex.seqProperties.tag = yTagQuestionMark of yamlScalar: - ex.scalarProperties.anchor = val.a + if c.style != asNone: ex.scalarProperties.anchor = val.a if ts == tsNone and guessType(ex.scalarContent) != yTypeNull: ex.scalarProperties.tag = yTagQuestionMark else: discard c.put = origPut c.put(ex) representChild(value[], childTagStyle, c) + if c.style == asNone: c.refs[p] = (val.a, false) proc representChild*[T](value: Option[T], ts: TagStyle, c: SerializationContext) =