From 4ca4b2c87e2ff8602bcd03f5b169fd85794e8baa Mon Sep 17 00:00:00 2001 From: Felix Krause Date: Mon, 4 Apr 2016 21:21:24 +0200 Subject: [PATCH] Support nil strings and seqs --- doc/serialization.txt | 7 +++++- private/serialization.nim | 45 +++++++++++++++++++++++++++++++++++ private/tagLibrary.nim | 4 +++- test/serializing.nim | 50 +++++++++++++++++++++++++++++---------- yaml.nim | 17 +++++++++++++ 5 files changed, 108 insertions(+), 15 deletions(-) diff --git a/doc/serialization.txt b/doc/serialization.txt index 22b9283..0b6b346 100644 --- a/doc/serialization.txt +++ b/doc/serialization.txt @@ -62,7 +62,9 @@ supported. There is currently no problem with ``float``, because it is always a ``float64``. ``string`` is supported and one of the few Nim types which directly map to a -standard YAML type. ``char`` is also supported. +standard YAML type. NimYAML is able to handle strings that are ``nil``, they +will be serialized with the special tag ``!nim:nil:string``. ``char`` is also +supported. To support new scalar types, you must implement the ``constructObject()`` and ``representObject()`` procs on that type (see below). @@ -86,6 +88,9 @@ cannot be loaded to Nim collection types. For example, this sequence: Cannot be loaded to a Nim ``seq``. For this reason, you cannot load YAML's native ``!!map`` and ``!!seq`` types directly into Nim types. +Nim ``seq`` types may be ``nil``. This is handled by serializing them to an +empty scalar with the tag ``!nim:nil:seq``. + Reference Types --------------- diff --git a/private/serialization.nim b/private/serialization.nim index 6c96ebf..60504c6 100644 --- a/private/serialization.nim +++ b/private/serialization.nim @@ -488,6 +488,37 @@ proc constructChild*[T](s: var YamlStream, c: ConstructionContext, else: assert false 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() @@ -527,6 +558,20 @@ proc representChild*[O](value: O, ts: TagStyle, c: SerializationContext): RawYamlStream = result = representObject(value, ts, c, presentTag(O, ts)) +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 value == nil: diff --git a/private/tagLibrary.nim b/private/tagLibrary.nim index 588e809..b6545fd 100644 --- a/private/tagLibrary.nim +++ b/private/tagLibrary.nim @@ -78,4 +78,6 @@ proc initSerializationTagLibrary(): TagLibrary = result.tags["tag:yaml.org,2002:timestamp"] = yTagTimestamp result.tags["tag:yaml.org,2002:value"] = yTagValue result.tags["tag:yaml.org,2002:binary"] = yTagBinary - result.tags["!nim:field"] = yTagNimField \ No newline at end of file + result.tags["!nim:field"] = yTagNimField + result.tags["!nim:nil:string"] = yTagNimNilString + result.tags["!nim:nil:seq"] = yTagNimNilSeq \ No newline at end of file diff --git a/test/serializing.nim b/test/serializing.nim index 5791640..dbdc5a4 100644 --- a/test/serializing.nim +++ b/test/serializing.nim @@ -93,6 +93,18 @@ suite "Serialization": except: gotException = true assert gotException, "Expected exception, got none." + test "Serialization: Load nil string": + let input = newStringStream("!nim:nil:string \"\"") + var result: string + load(input, result) + assert isNil(result) + + test "Serialization: Dump nil string": + let input: string = nil + var output = newStringStream() + dump(input, output, tsNone, asTidy, blockOnly) + assertStringEqual "%YAML 1.2\n--- \n!nim:nil:string \"\"", output.data + test "Serialization: Load string sequence": let input = newStringStream(" - a\n - b") var result: seq[string] @@ -101,12 +113,24 @@ suite "Serialization": assert result[0] == "a" assert result[1] == "b" - test "Serialization: Represent string sequence": + test "Serialization: Dump string sequence": var input = @["a", "b"] var output = newStringStream() dump(input, output, tsNone, asTidy, blockOnly) assertStringEqual "%YAML 1.2\n--- \n- a\n- b", output.data + test "Serialization: Load nil seq": + let input = newStringStream("!nim:nil:seq \"\"") + var result: seq[int] + load(input, result) + assert isNil(result) + + test "Serialization: Dump nil seq": + let input: seq[int] = nil + var output = newStringStream() + dump(input, output, tsNone, asTidy, blockOnly) + assertStringEqual "%YAML 1.2\n--- \n!nim:nil:seq \"\"", output.data + test "Serialization: Load char set": let input = newStringStream("- a\n- b") var result: set[char] @@ -115,7 +139,7 @@ suite "Serialization": assert 'a' in result assert 'b' in result - test "Serialization: Represent char set": + test "Serialization: Dump char set": var input = {'a', 'b'} var output = newStringStream() dump(input, output, tsNone, asTidy, blockOnly) @@ -129,7 +153,7 @@ suite "Serialization": assert result[1] == 42 assert result[2] == 47 - test "Serialization: Represent array": + test "Serialization: Dump array": let input = [23'i32, 42'i32, 47'i32] var output = newStringStream() dump(input, output, tsNone, asTidy, blockOnly) @@ -143,7 +167,7 @@ suite "Serialization": assert result[23] == "dreiundzwanzig" assert result[42] == "zweiundvierzig" - test "Serialization: Represent Table[int, string]": + test "Serialization: Dump Table[int, string]": var input = initTable[int32, string]() input[23] = "dreiundzwanzig" input[42] = "zweiundvierzig" @@ -168,7 +192,7 @@ suite "Serialization": else: assert false i.inc() - test "Serialization: Represent OrderedTable[tuple[int32, int32], string]": + test "Serialization: Dump OrderedTable[tuple[int32, int32], string]": var input = initOrderedTable[tuple[a, b: int32], string]() input.add((a: 23'i32, b: 42'i32), "dreiundzwanzigzweiundvierzig") input.add((a: 13'i32, b: 47'i32), "dreizehnsiebenundvierzig") @@ -196,7 +220,7 @@ suite "Serialization": assert result[1] == @[4.int32, 5.int32] assert result[2] == @[6.int32] - test "Serialization: Represent Sequences in Sequence": + test "Serialization: Dump Sequences in Sequence": let input = @[@[1.int32, 2.int32, 3.int32], @[4.int32, 5.int32], @[6.int32]] var output = newStringStream() dump(input, output, tsNone) @@ -212,7 +236,7 @@ suite "Serialization": assert result[1] == tlGreen assert result[2] == tlYellow - test "Serialization: Represent Enum": + test "Serialization: Dump Enum": let input = @[tlRed, tlGreen, tlYellow] var output = newStringStream() dump(input, output, tsNone, asTidy, blockOnly) @@ -227,7 +251,7 @@ suite "Serialization": assert result.i == 42 assert result.b == true - test "Serialization: Represent Tuple": + test "Serialization: Dump Tuple": let input = (str: "value", i: 42.int32, b: true) var output = newStringStream() dump(input, output, tsNone) @@ -241,7 +265,7 @@ suite "Serialization": assert result.surname == "Pan" assert result.age == 12 - test "Serialization: Represent custom object": + test "Serialization: Dump custom object": let input = Person(firstnamechar: 'P', surname: "Pan", age: 12) var output = newStringStream() dump(input, output, tsNone, asTidy, blockOnly) @@ -256,7 +280,7 @@ suite "Serialization": assert result[0] == "one" assert result[1] == "two" - test "Serialization: Represent sequence with explicit tags": + test "Serialization: Dump sequence with explicit tags": let input = @["one", "two"] var output = newStringStream() dump(input, output, tsAll, asTidy, blockOnly) @@ -272,7 +296,7 @@ suite "Serialization": assert result.surname == "Pan" assert result.age == 12 - test "Serialization: Represent custom object with explicit root tag": + test "Serialization: Dump custom object with explicit root tag": let input = Person(firstnamechar: 'P', surname: "Pan", age: 12) var output = newStringStream() dump(input, output, tsRootOnly, asTidy, blockOnly) @@ -280,7 +304,7 @@ suite "Serialization": "--- !nim:custom:Person \nfirstnamechar: P\nsurname: Pan\nage: 12", output.data) - test "Serialization: Represent cyclic data structure": + test "Serialization: Dump cyclic data structure": var a = newNode("a") b = newNode("b") @@ -342,7 +366,7 @@ next: assert(result[0] == nil) assert(result[1][] == "~") - test "Serialization: Represent nil values": + test "Serialization: Dump nil values": var input = newSeq[ref string]() input.add(nil) input.add(new string) diff --git a/yaml.nim b/yaml.nim index 0ff0c2c..792f304 100644 --- a/yaml.nim +++ b/yaml.nim @@ -369,6 +369,11 @@ const yTagNimField* : TagId = 100.TagId ## \ ## This tag is used in serialization for the name of a field of an ## object. It may contain any string scalar that is a valid Nim symbol. + + yTagNimNilString* : TagId = 101.TagId ## for strings that are nil + yTagNimNilSeq* : TagId = 102.TagId ## \ + ## for seqs that are nil. This tag is used regardless of the seq's generic + ## type parameter. yFirstCustomTagId* : TagId = 1000.TagId ## \ ## The first ``TagId`` which should be assigned to an URI that does not @@ -571,6 +576,18 @@ proc constructChild*[T](s: var YamlStream, c: ConstructionContext, ## for constructing the value. The ``ConstructionContext`` is needed for ## potential child objects which may be refs. +proc constructChild*(s: var YamlStream, c: ConstructionContext, + result: var string) + {.raises: [YamlConstructionError, YamlStreamError].} + ## Constructs a Nim value that is a string from a part of a YAML stream. + ## This specialization takes care of possible nil strings. + +proc constructChild*[T](s: var YamlStream, c: ConstructionContext, + result: var seq[T]) + {.raises: [YamlConstructionError, YamlStreamError].} + ## Constructs a Nim value that is a string from a part of a YAML stream. + ## This specialization takes care of possible nil seqs. + proc constructChild*[O](s: var YamlStream, c: ConstructionContext, result: var ref O) {.raises: [YamlConstructionError, YamlStreamError].}