native.nim
1 # NimYAML - YAML implementation in Nim 2 # (c) Copyright 2016 - 2020 Felix Krause 3 # 4 # See the file "copying.txt", included in this 5 # distribution, for details about the copyright. 6 7 ## ================== 8 ## Module yaml/native 9 ## ================== 10 ## 11 ## This module transforms native Nim values into a stream of YAML events, 12 ## and vice versa. The procs of this module must be available for name binding 13 ## when using the loading and dumping APIs. A NimYAML consumer would rarely 14 ## call this module's procs directly. The main entry points to this API are 15 ## ``construct`` and ``represent``; all other procs are usually called via 16 ## instantiations of those two procs. 17 ## 18 ## You can extend the procs defined here with own procs to define custom 19 ## handling for native types. See the documentation on the NimYAML 20 ## website for more information. 21 22 import std / [tables, typetraits, strutils, macros, streams, times, parseutils, options] 23 import data, taglib, stream, private/internal, hints, annotations 24 export data, stream, macros, annotations, options 25 # *something* in here needs externally visible `==`(x,y: AnchorId), 26 # but I cannot figure out what. binding it would be the better option. 27 28 type 29 TagStyle* = enum 30 ## Whether object should be serialized with explicit tags. 31 ## 32 ## - ``tsNone``: No tags will be outputted unless necessary. 33 ## - ``tsRootOnly``: A tag will only be outputted for the root tag and 34 ## where necessary. 35 ## - ``tsAll``: Tags will be outputted for every object. 36 tsNone, tsRootOnly, tsAll 37 38 AnchorStyle* = enum 39 ## How ref object should be serialized. 40 ## 41 ## - ``asNone``: No anchors will be written. Values present at 42 ## multiple places in the content that is serialized will be 43 ## duplicated at every occurrence. If the content is cyclic, this 44 ## will raise a YamlSerializationError. 45 ## - ``asTidy``: Anchors will only be generated for objects that 46 ## actually occur more than once in the content to be serialized. 47 ## This is a bit slower and needs more memory than ``asAlways``. 48 ## - ``asAlways``: Achors will be generated for every ref object in the 49 ## content that is serialized, regardless of whether the object is 50 ## referenced again afterwards. 51 asNone, asTidy, asAlways 52 53 SerializationOptions* = object 54 tagStyle* : TagStyle = tsNone 55 anchorStyle*: AnchorStyle = asTidy 56 handles* : seq[tuple[handle, uriPrefix: string]] 57 58 SerializationContext* = object 59 ## Context information for the process of serializing YAML from Nim values. 60 refs: Table[pointer, tuple[a: Anchor, referenced: bool]] 61 emitTag: bool 62 nextAnchorId: string 63 options*: SerializationOptions 64 putImpl*: proc(ctx: var SerializationContext, e: Event) {.raises: [], closure.} 65 overridingScalarStyle*: ScalarStyle = ssAny 66 overridingCollectionStyle*: CollectionStyle = csAny 67 68 ConstructionContext* = object 69 ## Context information for the process of constructing Nim values from YAML. 70 input*: YamlStream 71 refs* : Table[Anchor, tuple[tag: Tag, p: pointer]] 72 73 YamlConstructionError* = object of YamlLoadingError 74 ## Exception that may be raised when constructing data objects from a 75 ## `YamlStream <#YamlStream>`_. The fields ``line``, ``column`` and 76 ## ``lineContent`` are only available if the costructing proc also does 77 ## parsing, because otherwise this information is not available to the 78 ## costruction proc. 79 80 YamlSerializationError* = object of ValueError 81 ## Exception that may be raised when serializing Nim values into YAML 82 ## stream events. 83 84 proc put*(ctx: var SerializationContext, e: Event) {.raises: [].} = 85 ctx.putImpl(ctx, e) 86 87 proc scalarStyleFor(ctx: var SerializationContext, t: typedesc): ScalarStyle = 88 if ctx.overridingScalarStyle != ssAny: 89 result = ctx.overridingScalarStyle 90 ctx.overridingScalarStyle = ssAny 91 else: 92 when compiles(t.hasCustomPragma(scalar)): 93 when t.hasCustomPragma(scalar): 94 result = t.getCustomPragmaVal(scalar) 95 else: result = ssAny 96 else: result = ssAny 97 ctx.overridingCollectionStyle = csAny 98 99 proc collectionStyleFor(ctx: var SerializationContext, t: typedesc): CollectionStyle = 100 if ctx.overridingCollectionStyle != csAny: 101 result = ctx.overridingCollectionStyle 102 ctx.overridingCollectionStyle = csAny 103 else: 104 when compiles(t.hasCustomPragma(collection)): 105 when t.hasCustomPragma(collection): 106 result = t.getCustomPragmaVal(collection) 107 else: result = csAny 108 else: result = csAny 109 ctx.overridingScalarStyle = ssAny 110 111 # forward declares 112 113 proc constructChild*[T]( 114 ctx : var ConstructionContext, 115 result: var T, 116 ) {.raises: [YamlConstructionError, YamlStreamError].} 117 ## Constructs an arbitrary Nim value from a part of a YAML stream. 118 ## The stream will advance until after the finishing token that was used 119 ## for constructing the value. The ``ConstructionContext`` is needed for 120 ## potential child objects which may be refs. 121 122 proc constructChild*( 123 ctx : var ConstructionContext, 124 result: var string, 125 ) {.raises: [YamlConstructionError, YamlStreamError].} 126 ## Constructs a Nim value that is a string from a part of a YAML stream. 127 ## This specialization takes care of possible nil strings. 128 129 proc constructChild*[T]( 130 ctx : var ConstructionContext, 131 result: var seq[T], 132 ) {.raises: [YamlConstructionError, YamlStreamError].} 133 ## Constructs a Nim value that is a string from a part of a YAML stream. 134 ## This specialization takes care of possible nil seqs. 135 136 proc constructChild*[O]( 137 ctx : var ConstructionContext, 138 result: var ref O, 139 ) {.raises: [YamlConstructionError, YamlStreamError].} 140 ## Constructs an arbitrary Nim value from a part of a YAML stream. 141 ## The stream will advance until after the finishing token that was used 142 ## for constructing the value. The object may be constructed from an alias 143 ## node which will be resolved using the ``ConstructionContext``. 144 145 proc representChild*[O]( 146 ctx : var SerializationContext, 147 value: ref O, 148 ) {.raises: [YamlSerializationError].} 149 ## Represents an arbitrary Nim reference value as YAML object. The object 150 ## may be represented as alias node if it is already present in the 151 ## ``SerializationContext``. 152 153 proc representChild*( 154 ctx : var SerializationContext, 155 value: string, 156 ) {.inline, raises: [].} 157 ## Represents a Nim string. Supports nil strings. 158 159 proc representChild*[O]( 160 ctx: var SerializationContext, 161 value: O, 162 ) {.raises: [YamlSerializationError].} 163 ## Represents an arbitrary Nim object as YAML object. 164 165 proc initConstructionContext*(input: YamlStream): ConstructionContext = 166 result = ConstructionContext( 167 input: input, 168 refs : initTable[Anchor, tuple[tag: Tag, p: pointer]](), 169 ) 170 171 proc initSerializationContext*( 172 options: SerializationOptions, 173 putImpl: proc(ctx: var SerializationContext, e: Event) {.raises: [], closure.} 174 ): SerializationContext = 175 result = SerializationContext( 176 refs: initTable[pointer, tuple[a: Anchor, referenced: bool]](), 177 emitTag: options.tagStyle != tsNone, 178 nextAnchorId: "a", 179 options: options, 180 putImpl: putImpl 181 ) 182 183 proc presentTag*(ctx: var SerializationContext, t: typedesc): Tag {.inline.} = 184 ## Get the Tag that represents the given type in the given style 185 if ctx.emitTag: 186 result = yamlTag(t) 187 if ctx.options.tagStyle == tsRootOnly: ctx.emitTag = false 188 else: 189 result = yTagQuestionMark 190 191 proc safeTagUri(tag: Tag): string {.raises: [].} = 192 try: 193 var uri = $tag 194 # '!' is not allowed inside a tag handle 195 if uri.len > 0 and uri[0] == '!': uri = uri[1..^1] 196 # ',' is not allowed after a tag handle in the suffix because it's a flow 197 # indicator 198 for i in countup(0, uri.len - 1): 199 if uri[i] == ',': uri[i] = ';' 200 return uri 201 except KeyError: 202 internalError("Unexpected KeyError for Tag " & $tag) 203 204 proc newYamlConstructionError*( 205 s: YamlStream, 206 mark: Mark, 207 msg: string, 208 ): ref YamlConstructionError = 209 result = newException(YamlConstructionError, msg) 210 result.mark = mark 211 if not s.getLastTokenContext(result.lineContent): 212 result.lineContent = "" 213 214 proc constructionError*(s: YamlStream, mark: Mark, msg: string): 215 ref YamlConstructionError = 216 return newYamlConstructionError(s, mark, msg) 217 218 template constructScalarItem*( 219 s: var YamlStream, 220 i: untyped, 221 t: typedesc, 222 content: untyped, 223 ) = 224 ## Helper template for implementing ``constructObject`` for types that 225 ## are constructed from a scalar. ``i`` is the identifier that holds 226 ## the scalar as ``Event`` in the content. Exceptions raised in 227 ## the content will be automatically caught and wrapped in 228 ## ``YamlConstructionError``, which will then be raised. 229 bind constructionError 230 let i = s.next() 231 if i.kind != yamlScalar: 232 raise constructionError(s, i.startPos, "Expected scalar") 233 try: content 234 except YamlConstructionError as e: raise e 235 except CatchableError as e: 236 var ce = constructionError(s, i.startPos, 237 "Cannot construct to " & name(t) & ": " & item.scalarContent & 238 "; error: " & e.msg) 239 ce.parent = e 240 raise ce 241 242 proc yamlTag*(T: typedesc[string]): Tag {.inline, noSideEffect, raises: [].} = 243 yTagString 244 245 proc constructObject*( 246 ctx : var ConstructionContext, 247 result: var string, 248 ) {.raises: [YamlConstructionError, YamlStreamError].} = 249 ## constructs a string from a YAML scalar 250 ctx.input.constructScalarItem(item, string): 251 result = item.scalarContent 252 253 proc representObject*( 254 ctx : var SerializationContext, 255 value: string, 256 tag : Tag, 257 ) {.raises: [].} = 258 ## represents a string as YAML scalar 259 ctx.put(scalarEvent(value, tag, yAnchorNone, ctx.scalarStyleFor(string))) 260 261 proc parseHex[T: int8|int16|int32|int64|uint8|uint16|uint32|uint64]( 262 s: YamlStream, mark: Mark, val: string 263 ): T = 264 result = 0 265 for i in 2..<val.len: 266 case val[i] 267 of '_': discard 268 of '0'..'9': result = result shl 4 or T(ord(val[i]) - ord('0')) 269 of 'a'..'f': result = result shl 4 or T(ord(val[i]) - ord('a') + 10) 270 of 'A'..'F': result = result shl 4 or T(ord(val[i]) - ord('A') + 10) 271 else: 272 raise s.constructionError(mark, "Invalid character in hex: " & 273 escape("" & val[i])) 274 275 proc parseOctal[T: int8|int16|int32|int64|uint8|uint16|uint32|uint64]( 276 s: YamlStream, mark: Mark, val: string 277 ): T = 278 for i in 2..<val.len: 279 case val[i] 280 of '_': discard 281 of '0'..'7': result = result shl 3 + T((ord(val[i]) - ord('0'))) 282 else: 283 raise s.constructionError(mark, "Invalid character in hex: " & 284 escape("" & val[i])) 285 286 type NumberStyle = enum 287 nsHex 288 nsOctal 289 nsDecimal 290 291 proc numberStyle(item: Event): NumberStyle = 292 if item.scalarContent[0] == '0' and item.scalarContent.len > 1: 293 if item.scalarContent[1] in {'x', 'X' }: return nsHex 294 if item.scalarContent[1] in {'o', 'O'}: return nsOctal 295 return nsDecimal 296 297 proc constructObject*[T: int8|int16|int32|int64]( 298 ctx : var ConstructionContext, 299 result: var T, 300 ) {.raises: [YamlConstructionError, YamlStreamError].} = 301 ## constructs an integer value from a YAML scalar 302 ctx.input.constructScalarItem(item, T): 303 case item.numberStyle 304 of nsHex: 305 result = parseHex[T](ctx.input, item.startPos, item.scalarContent) 306 of nsOctal: 307 result = parseOctal[T](ctx.input, item.startPos, item.scalarContent) 308 of nsDecimal: 309 let nInt = parseBiggestInt(item.scalarContent) 310 if nInt <= T.high: 311 # make sure we don't produce a range error 312 result = T(nInt) 313 else: 314 raise ctx.input.constructionError( 315 item.startPos, 316 "Cannot construct int; out of range: " & 317 $nInt & " for type " & T.name & " with max of: " & $T.high 318 ) 319 320 proc constructObject*( 321 ctx : var ConstructionContext, 322 result: var int, 323 ) {.raises: [YamlConstructionError, YamlStreamError], inline.} = 324 ## constructs an integer of architecture-defined length by loading it into 325 ## int32 and then converting it. 326 var i32Result: int32 327 ctx.constructObject(i32Result) 328 result = int(i32Result) 329 330 proc representObject*[T: int8|int16|int32|int64]( 331 ctx : var SerializationContext, 332 value: T, 333 tag : Tag, 334 ) {.raises: [].} = 335 ## represents an integer value as YAML scalar 336 ctx.put(scalarEvent($value, tag, yAnchorNone, ctx.scalarStyleFor(T))) 337 338 proc representObject*( 339 ctx : var SerializationContext, 340 value: int, 341 tag : Tag, 342 ) {.raises: [YamlSerializationError], inline.}= 343 ## represent an integer of architecture-defined length by casting it to int32. 344 ## on 64-bit systems, this may cause a RangeDefect. 345 346 # currently, sizeof(int) is at least sizeof(int32). 347 try: 348 ctx.put(scalarEvent( 349 $int32(value), tag, yAnchorNone, ctx.scalarStyleFor(int))) 350 except RangeDefect as rd: 351 var e = newException(YamlSerializationError, rd.msg) 352 e.parent = rd 353 raise e 354 355 when defined(JS): 356 type DefiniteUIntTypes = uint8 | uint16 | uint32 357 else: 358 type DefiniteUIntTypes = uint8 | uint16 | uint32 | uint64 359 360 proc constructObject*[T: DefiniteUIntTypes]( 361 ctx : var ConstructionContext, 362 result: var T, 363 ) {.raises: [YamlConstructionError, YamlStreamError].} = 364 ## construct an unsigned integer value from a YAML scalar 365 ctx.input.constructScalarItem(item, T): 366 case item.numberStyle 367 of nsHex: 368 result = parseHex[T](ctx.input, item.startPos, item.scalarContent) 369 of nsOctal: 370 result = parseOctal[T](ctx.input, item.startPos, item.scalarContent) 371 else: 372 let nUInt = parseBiggestUInt(item.scalarContent) 373 if nUInt <= T.high: 374 # make sure we don't produce a range error 375 result = T(nUInt) 376 else: 377 raise ctx.input.constructionError( 378 item.startPos, 379 "Cannot construct uint; out of range: " & 380 $nUInt & " for type " & T.name & " with max of: " & $T.high 381 ) 382 383 proc constructObject*( 384 ctx : var ConstructionContext, 385 result: var uint, 386 ) {.raises: [YamlConstructionError, YamlStreamError], inline.} = 387 ## represent an unsigned integer of architecture-defined length by loading it 388 ## into uint32 and then converting it. 389 var u32Result: uint32 390 ctx.constructObject(u32Result) 391 result = uint(u32Result) 392 393 when defined(JS): 394 # TODO: this is a dirty hack and may lead to overflows! 395 proc `$`(x: uint8|uint16|uint32|uint64|uint): string = 396 result = $BiggestInt(x) 397 398 proc representObject*[T: uint8|uint16|uint32|uint64]( 399 ctx : var SerializationContext, 400 value: T, 401 tag : Tag, 402 ) {.raises: [].} = 403 ## represents an unsigned integer value as YAML scalar 404 ctx.put(scalarEvent($value, tag, yAnchorNone, ctx.scalarStyleFor(T))) 405 406 proc representObject*( 407 ctx : var SerializationContext, 408 value: uint, 409 tag : Tag, 410 ) {.raises: [YamlSerializationError], inline.} = 411 ## represent an unsigned integer of architecture-defined length by casting it 412 ## to int32. on 64-bit systems, this may cause a RangeDefect. 413 try: 414 ctx.put(scalarEvent( 415 $uint32(value), tag, yAnchorNone, ctx.scalarStyleFor(uint))) 416 except RangeDefect as rd: 417 var e = newException(YamlSerializationError, rd.msg) 418 e.parent = rd 419 raise e 420 421 proc constructObject*[T: float|float32|float64]( 422 ctx : var ConstructionContext, 423 result: var T, 424 ) {.raises: [YamlConstructionError, YamlStreamError].} = 425 ## construct a float value from a YAML scalar 426 ctx.input.constructScalarItem(item, T): 427 let hint = guessType(item.scalarContent) 428 case hint 429 of yTypeFloat: 430 var res: BiggestFloat 431 discard parseBiggestFloat(item.scalarContent, res) 432 result = res 433 of yTypeInteger: 434 var res: BiggestFloat 435 discard parseBiggestFloat(item.scalarContent, res) 436 result = res 437 of yTypeFloatInf: 438 if item.scalarContent[0] == '-': result = NegInf 439 else: result = Inf 440 of yTypeFloatNaN: result = NaN 441 else: 442 raise ctx.input.constructionError( 443 item.startPos, 444 "Cannot construct to float: " & escape(item.scalarContent) 445 ) 446 447 proc representObject*[T: float|float32|float64]( 448 ctx : var SerializationContext, 449 value: T, 450 tag : Tag, 451 ) {.raises: [].} = 452 ## represents a float value as YAML scalar 453 case value 454 of Inf: ctx.put(scalarEvent(".inf", tag, yAnchorNone, ctx.scalarStyleFor(T))) 455 of NegInf: ctx.put(scalarEvent("-.inf", tag, yAnchorNone, ctx.scalarStyleFor(T))) 456 of NaN: ctx.put(scalarEvent(".nan", tag, yAnchorNone, ctx.scalarStyleFor(T))) 457 else: ctx.put(scalarEvent($value, tag, yAnchorNone, ctx.scalarStyleFor(T))) 458 459 proc yamlTag*(T: typedesc[bool]): Tag {.inline, raises: [].} = yTagBoolean 460 461 proc constructObject*( 462 ctx : var ConstructionContext, 463 result: var bool, 464 ) {.raises: [YamlConstructionError, YamlStreamError].} = 465 ## constructs a bool value from a YAML scalar 466 ctx.input.constructScalarItem(item, bool): 467 case guessType(item.scalarContent) 468 of yTypeBoolTrue: result = true 469 of yTypeBoolFalse: result = false 470 else: 471 raise ctx.input.constructionError( 472 item.startPos, 473 "Cannot construct to bool: " & escape(item.scalarContent) 474 ) 475 476 proc representObject*( 477 ctx : var SerializationContext, 478 value: bool, 479 tag : Tag, 480 ) {.raises: [].} = 481 ## represents a bool value as a YAML scalar 482 ctx.put(scalarEvent(if value: "true" else: "false", 483 tag, yAnchorNone, ctx.scalarStyleFor(bool))) 484 485 proc constructObject*( 486 ctx : var ConstructionContext, 487 result: var char, 488 ) {.raises: [YamlConstructionError, YamlStreamError].} = 489 ## constructs a char value from a YAML scalar 490 ctx.input.constructScalarItem(item, char): 491 if item.scalarContent.len != 1: 492 raise ctx.input.constructionError( 493 item.startPos, 494 "Cannot construct to char (length != 1): " & escape(item.scalarContent) 495 ) 496 else: result = item.scalarContent[0] 497 498 proc representObject*( 499 ctx : var SerializationContext, 500 value: char, 501 tag : Tag 502 ) {.raises: [].} = 503 ## represents a char value as YAML scalar 504 ctx.put(scalarEvent("" & value, tag, yAnchorNone, ctx.scalarStyleFor(char))) 505 506 proc yamlTag*(T: typedesc[Time]): Tag {.inline, raises: [].} = yTagTimestamp 507 508 proc constructObject*( 509 ctx : var ConstructionContext, 510 result: var Time, 511 ) {.raises: [YamlConstructionError, YamlStreamError].} = 512 ctx.input.constructScalarItem(item, Time): 513 if guessType(item.scalarContent) == yTypeTimestamp: 514 var 515 tmp = newStringOfCap(60) 516 pos = 8 517 c: char 518 while pos < item.scalarContent.len(): 519 c = item.scalarContent[pos] 520 if c in {' ', '\t', 'T', 't'}: break 521 inc(pos) 522 if pos == item.scalarContent.len(): 523 tmp.add(item.scalarContent) 524 tmp.add("T00:00:00+00:00") 525 else: 526 tmp.add(item.scalarContent[0 .. pos - 1]) 527 if c in {' ', '\t'}: 528 while true: 529 inc(pos) 530 c = item.scalarContent[pos] 531 if c notin {' ', '\t'}: break 532 else: inc(pos) 533 tmp.add("T") 534 let timeStart = pos 535 inc(pos, 7) 536 var fractionStart = -1 537 while pos < item.scalarContent.len(): 538 c = item.scalarContent[pos] 539 if c in {'+', '-', 'Z', ' ', '\t'}: break 540 elif c == '.': fractionStart = pos 541 inc(pos) 542 if fractionStart == -1: 543 tmp.add(item.scalarContent[timeStart .. pos - 1]) 544 else: 545 tmp.add(item.scalarContent[timeStart .. fractionStart - 1]) 546 if c in {'Z', ' ', '\t'}: tmp.add("+00:00") 547 else: 548 tmp.add(c) 549 inc(pos) 550 let tzStart = pos 551 inc(pos) 552 if pos < item.scalarContent.len() and item.scalarContent[pos] != ':': 553 inc(pos) 554 if pos - tzStart == 1: tmp.add('0') 555 tmp.add(item.scalarContent[tzStart .. pos - 1]) 556 if pos == item.scalarContent.len(): tmp.add(":00") 557 elif pos + 2 == item.scalarContent.len(): 558 tmp.add(":0") 559 tmp.add(item.scalarContent[pos + 1]) 560 else: 561 tmp.add(item.scalarContent[pos .. pos + 2]) 562 let info = tmp.parse("yyyy-M-d'T'H:mm:sszzz") 563 result = info.toTime() 564 else: 565 raise ctx.input.constructionError( 566 item.startPos, 567 "Not a parsable timestamp: " & escape(item.scalarContent) 568 ) 569 570 proc representObject*( 571 ctx : var SerializationContext, 572 value: Time, 573 tag : Tag, 574 ) {.raises: [].} = 575 let tmp = value.utc() 576 ctx.put(scalarEvent(tmp.format( 577 "yyyy-MM-dd'T'HH:mm:ss'Z'"), tag, yAnchorNone, ctx.scalarStyleFor(Time))) 578 579 proc yamlTag*[I](T: typedesc[seq[I]]): Tag {.inline, raises: [].} = 580 return nimTag("system:seq(" & safeTagUri(yamlTag(I)) & ')') 581 582 proc yamlTag*[I](T: typedesc[set[I]]): Tag {.inline, raises: [].} = 583 return nimTag("system:set(" & safeTagUri(yamlTag(I)) & ')') 584 585 proc constructObject*[T]( 586 ctx : var ConstructionContext, 587 result: var seq[T], 588 ) {.raises: [YamlConstructionError, YamlStreamError].} = 589 ## constructs a Nim seq from a YAML sequence 590 let event = ctx.input.next() 591 if event.kind != yamlStartSeq: 592 raise ctx.input.constructionError(event.startPos, "Expected sequence start") 593 result = newSeq[T]() 594 while ctx.input.peek().kind != yamlEndSeq: 595 var item: T 596 ctx.constructChild(item) 597 result.add(move(item)) 598 discard ctx.input.next() 599 600 proc constructObject*[T]( 601 ctx : var ConstructionContext, 602 result: var set[T], 603 ) {.raises: [YamlConstructionError, YamlStreamError].} = 604 ## constructs a Nim seq from a YAML sequence 605 let event = ctx.input.next() 606 if event.kind != yamlStartSeq: 607 raise ctx.input.constructionError(event.startPos, "Expected sequence start") 608 result = {} 609 while ctx.input.peek().kind != yamlEndSeq: 610 var item: T 611 ctx.constructChild(item) 612 result.incl(item) 613 discard ctx.input.next() 614 615 proc representObject*[T]( 616 ctx : var SerializationContext, 617 value: seq[T]|set[T], 618 tag : Tag, 619 ) {.raises: [YamlSerializationError].} = 620 ## represents a Nim seq as YAML sequence 621 ctx.put( 622 startSeqEvent(tag = tag, style = ctx.collectionStyleFor(type(value)))) 623 for item in value: ctx.representChild(item) 624 ctx.put(endSeqEvent()) 625 626 proc yamlTag*[I, V](T: typedesc[array[I, V]]): Tag {.inline, raises: [].} = 627 const rangeName = name(I) 628 return nimTag("system:array(" & rangeName[6..rangeName.high()] & ';' & 629 safeTagUri(yamlTag(V)) & ')') 630 631 proc constructObject*[I, T]( 632 ctx : var ConstructionContext, 633 result: var array[I, T], 634 ) {.raises: [YamlConstructionError, YamlStreamError].} = 635 ## constructs a Nim array from a YAML sequence 636 var event = ctx.input.next() 637 if event.kind != yamlStartSeq: 638 raise ctx.input.constructionError(event.startPos, "Expected sequence start") 639 for index in low(I)..high(I): 640 event = ctx.input.peek() 641 if event.kind == yamlEndSeq: 642 raise ctx.input.constructionError(event.startPos, "Too few array values") 643 ctx.constructChild(result[index]) 644 event = ctx.input.next() 645 if event.kind != yamlEndSeq: 646 raise ctx.input.constructionError(event.startPos, "Too many array values") 647 648 proc representObject*[I, T]( 649 ctx : var SerializationContext, 650 value: array[I, T], 651 tag : Tag, 652 ) {.raises: [YamlSerializationError].} = 653 ## represents a Nim array as YAML sequence 654 ctx.put(startSeqEvent(tag = tag, style = ctx.collectionStyleFor(array[I, T]))) 655 for item in value: ctx.representChild(item) 656 ctx.put(endSeqEvent()) 657 658 proc yamlTag*[K, V](T: typedesc[Table[K, V]]): Tag {.inline, raises: [].} = 659 return nimTag("tables:Table(" & safeTagUri(yamlTag(K)) & ';' & 660 safeTagUri(yamlTag(V)) & ")") 661 662 proc constructObject*[K, V]( 663 ctx : var ConstructionContext, 664 result: var Table[K, V], 665 ) {.raises: [YamlConstructionError, YamlStreamError].} = 666 ## constructs a Nim Table from a YAML mapping 667 let event = ctx.input.next() 668 if event.kind != yamlStartMap: 669 raise ctx.input.constructionError( 670 event.startPos, "Expected map start, got " & $event.kind 671 ) 672 result = initTable[K, V]() 673 while ctx.input.peek.kind != yamlEndMap: 674 var 675 key: K 676 value: V 677 ctx.constructChild(key) 678 ctx.constructChild(value) 679 if result.contains(key): 680 raise ctx.input.constructionError(event.startPos, "Duplicate table key!") 681 result[key] = value 682 discard ctx.input.next() 683 684 proc representObject*[K, V]( 685 ctx : var SerializationContext, 686 value: Table[K, V], 687 tag : Tag, 688 ) {.raises: [YamlSerializationError].} = 689 ## represents a Nim Table as YAML mapping 690 ctx.put( 691 startMapEvent(tag = tag, style = ctx.collectionStyleFor(Table[K, V]))) 692 for key, value in value.pairs: 693 ctx.representChild(key) 694 ctx.representChild(value) 695 ctx.put(endMapEvent()) 696 697 proc yamlTag*[K, V](T: typedesc[OrderedTable[K, V]]): Tag 698 {.inline, raises: [].} = 699 return nimTag("tables:OrderedTable(" & safeTagUri(yamlTag(K)) & ';' & 700 safeTagUri(yamlTag(V)) & ")") 701 702 proc constructObject*[K, V]( 703 ctx : var ConstructionContext, 704 result: var OrderedTable[K, V], 705 ) {.raises: [YamlConstructionError, YamlStreamError].} = 706 ## constructs a Nim OrderedTable from a YAML mapping 707 var event = ctx.input.next() 708 if event.kind != yamlStartSeq: 709 raise ctx.input.constructionError( 710 event.startPos, "Expected seq start, got " & $event.kind 711 ) 712 result = initOrderedTable[K, V]() 713 while ctx.input.peek.kind != yamlEndSeq: 714 var 715 key: K 716 value: V 717 event = ctx.input.next() 718 if event.kind != yamlStartMap: 719 raise ctx.input.constructionError( 720 event.startPos, "Expected map start, got " & $event.kind 721 ) 722 ctx.constructChild(key) 723 ctx.constructChild(value) 724 event = ctx.input.next() 725 if event.kind != yamlEndMap: 726 raise ctx.input.constructionError( 727 event.startPos, "Expected map end, got " & $event.kind 728 ) 729 if result.contains(key): 730 raise ctx.input.constructionError(event.startPos, "Duplicate table key!") 731 result[move(key)] = move(value) 732 discard ctx.input.next() 733 734 proc representObject*[K, V]( 735 ctx : var SerializationContext, 736 value: OrderedTable[K, V], 737 tag : Tag, 738 ) {.raises: [YamlSerializationError].} = 739 ctx.put(startSeqEvent( 740 tag = tag, style = ctx.collectionStyleFor(OrderedTable[K, V]))) 741 for key, value in value.pairs: 742 ctx.put(startMapEvent()) 743 ctx.representChild(key) 744 ctx.representChild(value) 745 ctx.put(endMapEvent()) 746 ctx.put(endSeqEvent()) 747 748 proc yamlTag*(T: typedesc[object|enum]): 749 Tag {.inline, raises: [].} = 750 return nimTag("custom:" & (typetraits.name(type(T)))) 751 752 proc yamlTag*(T: typedesc[tuple]): 753 Tag {.inline, raises: [].} = 754 var 755 i: T 756 uri = nimyamlTagRepositoryPrefix & "tuple(" 757 first = true 758 for name, value in fieldPairs(i): 759 if first: first = false 760 else: uri.add(",") 761 uri.add(safeTagUri(yamlTag(type(value)))) 762 uri.add(")") 763 return Tag(uri) 764 765 iterator recListItems(n: NimNode): NimNode = 766 if n.kind == nnkRecList: 767 for item in n.children: yield item 768 else: yield n 769 770 proc recListLen(n: NimNode): int {.compileTime.} = 771 if n.kind == nnkRecList: result = n.len 772 else: result = 1 773 774 proc recListNode(n: NimNode): NimNode {.compileTime.} = 775 if n.kind == nnkRecList: result = n[0] 776 else: result = n 777 778 proc parentType(tDesc: NimNode): NimNode {.compileTime.} = 779 var name: NimNode 780 case tDesc[1].kind 781 of nnkEmpty: return nil 782 of nnkBracketExpr: 783 # happens when parent type is `ref X` 784 name = tDesc[1][1] 785 of nnkObjectTy, nnkSym: 786 name = tDesc[1] 787 else: 788 return nil 789 result = newNimNode(nnkBracketExpr) 790 result.add(bindSym("typeDesc")) 791 result.add(name) 792 793 proc fieldCount(t: NimNode): int {.compiletime.} = 794 result = 0 795 var tTypedesc: NimNode 796 if t.kind == nnkSym: 797 tTypedesc = getType(t) 798 else: 799 tTypedesc = t 800 801 let tDesc = getType(tTypedesc[1]) 802 if tDesc.kind == nnkBracketExpr: 803 # tuple 804 result = tDesc.len - 1 805 else: 806 # object 807 let tParent = parentType(tDesc) 808 if tParent != nil: 809 # inherited fields 810 result += fieldCount(tParent) 811 for child in tDesc[2].children: 812 inc(result) 813 if child.kind == nnkRecCase: 814 for bIndex in 1..<len(child): 815 var increment = 0 816 case child[bIndex].kind 817 of nnkOfBranch: 818 let content = child[bIndex][len(child[bIndex])-1] 819 # We cannot assume that child[bIndex][1] is a RecList due to 820 # a one-liner like 'of akDog: barkometer' not resulting in a 821 # RecList but in an Ident node. 822 case content.kind 823 of nnkRecList: 824 increment = len(content) 825 else: 826 increment = 1 827 of nnkElse: 828 # Same goes for the else branch. 829 case child[bIndex][0].kind 830 of nnkRecList: 831 increment = len(child[bIndex][0]) 832 else: 833 increment = 1 834 else: 835 internalError("Unexpected child kind: " & $child[bIndex].kind) 836 inc(result, increment) 837 838 macro matchMatrix(t: typedesc): untyped = 839 let numFields = fieldCount(t) 840 if numFields == 0: 841 result = quote do: 842 (seq[bool])(@[]) 843 return 844 845 result = newNimNode(nnkBracket) 846 for i in 0..<numFields: 847 result.add(newLit(false)) 848 849 proc checkDuplicate( 850 s : NimNode, 851 tName : string, 852 name : string, 853 i : int, 854 matched: NimNode, 855 m : NimNode, 856 ): NimNode {.compileTime.} = 857 result = newIfStmt((newNimNode(nnkBracketExpr).add(matched, newLit(i)), 858 newNimNode(nnkRaiseStmt).add(newCall(bindSym("constructionError"), s, m, 859 newLit("While constructing " & tName & ": Duplicate field: " & 860 escape(name)))))) 861 862 proc input(ctx: NimNode): NimNode {.compileTime.} = 863 return newDotExpr(ctx, ident("input")) 864 865 proc hasSparse(t: typedesc): bool {.compileTime.} = 866 when compiles(t.hasCustomPragma(sparse)): 867 return t.hasCustomPragma(sparse) 868 else: 869 return false 870 871 proc getOptionInner(fType: NimNode): NimNode {.compileTime.} = 872 if fType.kind == nnkBracketExpr and len(fType) == 2 and 873 fType[0].kind == nnkSym: 874 return fType[1] 875 else: return nil 876 877 proc checkMissing( 878 s, t : NimNode, 879 tName : string, 880 field : NimNode, 881 i : int, 882 matched: NimNode, 883 o, m : NimNode, 884 ): 885 NimNode {.compileTime.} = 886 let 887 fType = getTypeInst(field) 888 fName = escape($field) 889 optionInner = getOptionInner(fType) 890 result = quote do: 891 when not `o`.`field`.hasCustomPragma(transient): 892 if not `matched`[`i`]: 893 when `o`.`field`.hasCustomPragma(defaultVal): 894 `o`.`field` = `o`.`field`.getCustomPragmaVal(defaultVal) 895 elif hasSparse(`t`) and `o`.`field` is Option: 896 `o`.`field` = none[`optionInner`]() 897 else: 898 raise constructionError(`s`, `m`, "While constructing " & `tName` & 899 ": Missing field: " & `fName`) 900 901 proc markAsFound(i: int, matched: NimNode): NimNode {.compileTime.} = 902 newAssignment(newNimNode(nnkBracketExpr).add(matched, newLit(i)), 903 newLit(true)) 904 905 proc ifNotTransient( 906 o, field : NimNode, 907 content : openarray[NimNode], 908 elseError: bool, 909 s, m : NimNode, 910 tName: string = "", 911 fName: string = "", 912 ): 913 NimNode {.compileTime.} = 914 var stmts = newStmtList(content) 915 if elseError: 916 result = quote do: 917 when `o`.`field`.hasCustomPragma(transient): 918 raise constructionError(`s`, `m`, "While constructing " & `tName` & 919 ": Field \"" & `fName` & "\" is transient and may not occur in input") 920 else: 921 `stmts` 922 else: 923 result = quote do: 924 when not `o`.`field`.hasCustomPragma(transient): 925 `stmts` 926 927 proc recEnsureAllFieldsPresent( 928 s, tDecl, o: NimNode, 929 matched, m : NimNode, 930 tName: string, 931 field: var int, 932 stmt : NimNode, 933 ) {.compileTime.} = 934 var 935 tDesc = getType(tDecl[1]) 936 tParent = parentType(tDesc) 937 if tParent != nil: 938 recEnsureAllFieldsPresent(s, tParent, o, matched, m, tName, field, stmt) 939 for child in tDesc[2].children: 940 if child.kind == nnkRecCase: 941 stmt.add(checkMissing( 942 s, tDecl, tName, child[0], field, matched, o, m)) 943 for bIndex in 1 .. len(child) - 1: 944 let discChecks = newStmtList() 945 var 946 curValues = newNimNode(nnkCurly) 947 recListIndex = 0 948 case child[bIndex].kind 949 of nnkOfBranch: 950 while recListIndex < child[bIndex].len - 1: 951 expectKind(child[bIndex][recListIndex], nnkIntLit) 952 curValues.add(child[bIndex][recListIndex]) 953 inc(recListIndex) 954 of nnkElse: discard 955 else: internalError("Unexpected child kind: " & $child[bIndex].kind) 956 for item in child[bIndex][recListIndex].recListItems: 957 inc(field) 958 discChecks.add(checkMissing( 959 s, tDecl, tName, item, field, matched, o, m)) 960 stmt.add(newIfStmt((infix(newDotExpr(o, newIdentNode($child[0])), 961 "in", curValues), discChecks))) 962 else: 963 stmt.add(checkMissing(s, tDecl, tName, child, field, matched, o, m)) 964 inc(field) 965 966 proc getTypedescType(t: typedesc): NimNode {.compileTime.} = 967 ## workaround for https://github.com/nim-lang/Nim/issues/23112 968 result = getType(t) 969 while result.kind == nnkBracketExpr and result[0].eqIdent"typeDesc": 970 result = getType(result[1]) 971 972 macro ensureAllFieldsPresent( 973 s : YamlStream, 974 t : typedesc, 975 o : typed, 976 matched: typed, 977 m : Mark, 978 ) = 979 result = newStmtList() 980 let 981 tDecl = getType(t) 982 tName = $tDecl[1] 983 var field = 0 984 recEnsureAllFieldsPresent(s, tDecl, o, matched, m, tName, field, result) 985 986 proc skipOverValue(s: var YamlStream) = 987 var e = s.next() 988 var depth = int(e.kind in {yamlStartMap, yamlStartSeq}) 989 while depth > 0: 990 case s.next().kind 991 of yamlStartMap, yamlStartSeq: inc(depth) 992 of yamlEndMap, yamlEndSeq: dec(depth) 993 of yamlScalar, yamlAlias: discard 994 else: internalError("Unexpected event kind.") 995 996 proc addFieldCases( 997 tDecl, context : NimNode, 998 name, o, matched: NimNode, 999 failOnUnknown, m: NimNode, 1000 tName : string, 1001 caseStmt : NimNode, 1002 fieldIndex: var int, 1003 ) {.compileTime.} = 1004 var 1005 tDesc = getType(tDecl[1]) 1006 tParent = parentType(tDesc) 1007 if tParent != nil: 1008 addFieldCases(tParent, context, name, o, matched, failOnUnknown, m, tName, caseStmt, fieldIndex) 1009 for child in tDesc[2].children: 1010 if child.kind == nnkRecCase: 1011 let 1012 discriminant = newDotExpr(o, newIdentNode($child[0])) 1013 discType = newCall("type", discriminant) 1014 var disOb = newNimNode(nnkOfBranch).add(newStrLitNode($child[0])) 1015 var objConstr = newNimNode(nnkObjConstr).add(newCall("type", o)) 1016 objConstr.add(newColonExpr(newIdentNode($child[0]), newIdentNode( 1017 "value"))) 1018 for otherChild in tDesc[2].children: 1019 if otherChild == child: 1020 continue 1021 if otherChild.kind != nnkSym: 1022 error("Unexpected kind of field '" & $otherChild[0] & 1023 "': " & $otherChild.kind) 1024 objConstr.add(newColonExpr(newIdentNode($otherChild), newDotExpr(o, 1025 newIdentNode($otherChild)))) 1026 disOb.add(newStmtList( 1027 checkDuplicate(input(context), tName, $child[0], fieldIndex, matched, m), 1028 newNimNode(nnkVarSection).add( 1029 newNimNode(nnkIdentDefs).add( 1030 newIdentNode("value"), discType, newEmptyNode())), 1031 newCall("constructChild", context, newIdentNode("value")), 1032 newAssignment(o, objConstr), 1033 markAsFound(fieldIndex, matched))) 1034 caseStmt.add(disOb) 1035 var alreadyUsedSet = newNimNode(nnkCurly) 1036 for bIndex in 1 .. len(child) - 1: 1037 var recListIndex = 0 1038 var discTest: NimNode 1039 case child[bIndex].kind 1040 of nnkOfBranch: 1041 discTest = newNimNode(nnkCurly) 1042 while recListIndex < child[bIndex].len - 1: 1043 yAssert child[bIndex][recListIndex].kind == nnkIntLit 1044 discTest.add(child[bIndex][recListIndex]) 1045 alreadyUsedSet.add(child[bIndex][recListIndex]) 1046 inc(recListIndex) 1047 discTest = infix(discriminant, "in", discTest) 1048 of nnkElse: 1049 discTest = infix(discriminant, "notin", alreadyUsedSet) 1050 else: 1051 internalError("Unexpected child kind: " & $child[bIndex].kind) 1052 1053 for item in child[bIndex][recListIndex].recListItems: 1054 inc(fieldIndex) 1055 yAssert item.kind == nnkSym 1056 var ob = newNimNode(nnkOfBranch).add(newStrLitNode($item)) 1057 let field = newDotExpr(o, newIdentNode($item)) 1058 var ifStmt = newIfStmt((cond: discTest, body: newStmtList( 1059 newCall("constructChild", context, field)))) 1060 ifStmt.add(newNimNode(nnkElse).add(newNimNode(nnkRaiseStmt).add( 1061 newCall(bindSym("constructionError"), input(context), m, 1062 infix(newStrLitNode("Field " & $item & " not allowed for " & 1063 $child[0] & " == "), "&", prefix(discriminant, "$")))))) 1064 ob.add(ifNotTransient(o, item, 1065 [checkDuplicate(input(context), tName, $item, fieldIndex, matched, m), 1066 ifStmt, markAsFound(fieldIndex, matched)], true, 1067 input(context), m, tName, $item)) 1068 caseStmt.add(ob) 1069 else: 1070 yAssert child.kind == nnkSym 1071 var ob = newNimNode(nnkOfBranch).add(newStrLitNode($child)) 1072 let field = newDotExpr(o, newIdentNode($child)) 1073 ob.add(ifNotTransient(o, child, 1074 [checkDuplicate(input(context), tName, $child, fieldIndex, matched, m), 1075 newCall("constructChild", context, field), 1076 markAsFound(fieldIndex, matched)], true, input(context), m, tName, $child)) 1077 caseStmt.add(ob) 1078 inc(fieldIndex) 1079 1080 macro constructFieldValue( 1081 t: typedesc, 1082 context, name, o, matched: untyped, 1083 failOnUnknown: bool, 1084 m: untyped, 1085 ) = 1086 let 1087 tDecl = getType(t) 1088 tName = $tDecl[1] 1089 result = newStmtList() 1090 var caseStmt = newNimNode(nnkCaseStmt).add(name) 1091 var fieldIndex = 0 1092 addFieldCases(tDecl, context, name, o, matched, failOnUnknown, m, tName, caseStmt, fieldIndex) 1093 caseStmt.add(newNimNode(nnkElse).add(newNimNode(nnkWhenStmt).add( 1094 newNimNode(nnkElifBranch).add(failOnUnknown, 1095 newNimNode(nnkRaiseStmt).add( 1096 newCall(bindSym("constructionError"), input(context), m, 1097 infix(newLit("While constructing " & tName & ": Unknown field: "), "&", 1098 newCall(bindSym("escape"), name))))) 1099 ).add(newNimNode(nnkElse).add( 1100 newCall(bindSym("skipOverValue"), input(context)) 1101 )))) 1102 result.add(caseStmt) 1103 1104 proc isVariantObject(t: NimNode): bool {.compileTime.} = 1105 var 1106 tResolved: NimNode 1107 tDesc: NimNode 1108 if t.kind == nnkSym: 1109 tResolved = getType(t) 1110 else: 1111 tResolved = t 1112 if tResolved.kind == nnkBracketExpr and tResolved[0].strVal == "typeDesc": 1113 tDesc = getType(tResolved[1]) 1114 else: 1115 tDesc = tResolved 1116 if tDesc.kind != nnkObjectTy: return false 1117 let tParent = parentType(tDesc) 1118 if tParent != nil: 1119 if isVariantObject(tParent): return true 1120 for child in tDesc[2].children: 1121 if child.kind == nnkRecCase: return true 1122 return false 1123 1124 proc hasIgnore(t: typedesc): bool {.compileTime.} = 1125 when compiles(t.hasCustomPragma(ignore)): 1126 return t.hasCustomPragma(ignore) 1127 else: 1128 return false 1129 1130 proc constructObjectDefault*( 1131 ctx : var ConstructionContext, 1132 result: var RootObj, 1133 ) = 1134 # specialization of generic proc for RootObj, doesn't do anything 1135 return 1136 1137 proc constructObjectDefault*[O: object|tuple]( 1138 ctx : var ConstructionContext, 1139 result: var O, 1140 ) {.raises: [YamlConstructionError, YamlStreamError].} = 1141 ## Constructs a Nim object or tuple from a YAML mapping. 1142 ## This is the default implementation for custom objects and tuples and should 1143 ## not be redefined. If you are adding a custom constructObject() 1144 ## implementation, you can use this proc to call the default implementation 1145 ## within it. 1146 var matched = matchMatrix(O) 1147 var e = ctx.input.next() 1148 const 1149 startKind = when isVariantObject(getType(O)): yamlStartSeq else: yamlStartMap 1150 endKind = when isVariantObject(getType(O)): yamlEndSeq else: yamlEndMap 1151 if e.kind != startKind: 1152 raise ctx.input.constructionError( 1153 e.startPos, 1154 "While constructing " & typetraits.name(O) & 1155 ": Expected " & $startKind & ", got " & $e.kind 1156 ) 1157 let startPos = e.startPos 1158 when hasIgnore(O): 1159 const ignoredKeyList = O.getCustomPragmaVal(ignore) 1160 const failOnUnknown = len(ignoredKeyList) > 0 1161 else: 1162 const failOnUnknown = true 1163 while ctx.input.peek.kind != endKind: 1164 e = ctx.input.next() 1165 when isVariantObject(getType(O)): 1166 if e.kind != yamlStartMap: 1167 raise ctx.input.constructionError( 1168 e.startPos, "Expected single-pair map, got " & $e.kind 1169 ) 1170 e = ctx.input.next() 1171 if e.kind != yamlScalar: 1172 raise ctx.input.constructionError( 1173 e.startPos, "Expected field name, got " & $e.kind 1174 ) 1175 let name = e.scalarContent 1176 when result is tuple: 1177 var i = 0 1178 var found = false 1179 for fname, value in fieldPairs(result): 1180 if fname == name: 1181 if matched[i]: 1182 raise ctx.input.constructionError( 1183 e.startPos, "While constructing " & 1184 typetraits.name(O) & ": Duplicate field: " & escape(name) 1185 ) 1186 ctx.constructChild(value) 1187 matched[i] = true 1188 found = true 1189 break 1190 inc(i) 1191 when failOnUnknown: 1192 if not found: 1193 raise ctx.input.constructionError( 1194 e.startPos, "While constructing " & 1195 typetraits.name(O) & ": Unknown field: " & escape(name) 1196 ) 1197 else: 1198 when hasIgnore(O) and failOnUnknown: 1199 if name notin ignoredKeyList: 1200 constructFieldValue(O, ctx, name, result, matched, failOnUnknown, e.startPos) 1201 else: 1202 skipOverValue(ctx.input) 1203 else: 1204 constructFieldValue(O, ctx, name, result, matched, failOnUnknown, e.startPos) 1205 when isVariantObject(getType(O)): 1206 e = ctx.input.next() 1207 if e.kind != yamlEndMap: 1208 raise ctx.input.constructionError( 1209 e.startPos, "Expected end of single-pair map, got " & $e.kind 1210 ) 1211 discard ctx.input.next() 1212 when result is tuple: 1213 var i = 0 1214 for fname, value in fieldPairs(result): 1215 if not matched[i]: 1216 raise ctx.input.constructionError(startPos, "While constructing " & 1217 typetraits.name(O) & ": Missing field: " & escape(fname)) 1218 inc(i) 1219 else: ensureAllFieldsPresent(ctx.input, O, result, matched, startPos) 1220 1221 proc constructObject*[O: object|tuple]( 1222 ctx : var ConstructionContext, 1223 result: var O, 1224 ) {.raises: [YamlConstructionError, YamlStreamError].} = 1225 ## Overridable default implementation for custom object and tuple types 1226 ctx.constructObjectDefault(result) 1227 1228 proc recGenFieldRepresenters( 1229 tDecl, value: NimNode, 1230 isVO : bool, 1231 fieldIndex : var int16, 1232 result : NimNode, 1233 ) {.compileTime.} = 1234 let 1235 tDesc = getType(tDecl[1]) 1236 tParent = parentType(tDesc) 1237 if tParent != nil: 1238 recGenFieldRepresenters(tParent, value, isVO, fieldIndex, result) 1239 for child in tDesc[2].children: 1240 if child.kind == nnkRecCase: 1241 let 1242 fieldName = $child[0] 1243 fieldAccessor = newDotExpr(value, newIdentNode(fieldName)) 1244 result.add(quote do: 1245 ctx.put(startMapEvent()) 1246 ctx.put(scalarEvent( 1247 `fieldName`, 1248 tag = if ctx.emitTag: yTagNimField else: yTagQuestionMark 1249 )) 1250 when `fieldAccessor`.hasCustomPragma(scalar): 1251 ctx.overridingScalarStyle = `fieldAccessor`.getCustomPragmaVal(scalar) 1252 when `fieldAccessor`.hasCustomPragma(collection): 1253 ctx.overridingCollectionStyle = `fieldAccessor`.getCustomPragmaVal(collection) 1254 ctx.representChild(`fieldAccessor`) 1255 ctx.put(endMapEvent()) 1256 ) 1257 let enumName = $getTypeInst(child[0]) 1258 var caseStmt = newNimNode(nnkCaseStmt).add(fieldAccessor) 1259 for bIndex in 1 .. len(child) - 1: 1260 var curBranch: NimNode 1261 var recListIndex = 0 1262 case child[bIndex].kind 1263 of nnkOfBranch: 1264 curBranch = newNimNode(nnkOfBranch) 1265 while recListIndex < child[bIndex].len - 1: 1266 expectKind(child[bIndex][recListIndex], nnkIntLit) 1267 curBranch.add(newCall(enumName, newLit(child[bIndex][recListIndex].intVal))) 1268 inc(recListIndex) 1269 of nnkElse: 1270 curBranch = newNimNode(nnkElse) 1271 else: 1272 internalError("Unexpected child kind: " & $child[bIndex].kind) 1273 var curStmtList = newStmtList() 1274 if child[bIndex][recListIndex].recListLen > 0: 1275 for item in child[bIndex][recListIndex].recListItems(): 1276 inc(fieldIndex) 1277 let 1278 name = $item 1279 itemAccessor = newDotExpr(value, newIdentNode(name)) 1280 curStmtList.add(quote do: 1281 when not `itemAccessor`.hasCustomPragma(transient): 1282 ctx.put(startMapEvent()) 1283 ctx.put(scalarEvent( 1284 `name`, 1285 tag = if ctx.emitTag: yTagNimField else: yTagQuestionMark 1286 )) 1287 when `itemAccessor`.hasCustomPragma(scalar): 1288 ctx.overridingScalarStyle = `itemAccessor`.getCustomPragmaVal(scalar) 1289 when `itemAccessor`.hasCustomPragma(collection): 1290 ctx.overridingCollectionStyle = `itemAccessor`.getCustomPragmaVal(collection) 1291 ctx.representChild(`itemAccessor`) 1292 ctx.put(endMapEvent()) 1293 ) 1294 else: 1295 curStmtList.add(newNimNode(nnkDiscardStmt).add(newEmptyNode())) 1296 curBranch.add(curStmtList) 1297 caseStmt.add(curBranch) 1298 result.add(caseStmt) 1299 else: 1300 let 1301 name = $child 1302 templName = genSym(nskTemplate) 1303 childAccessor = newDotExpr(value, newIdentNode(name)) 1304 result.add(quote do: 1305 template `templName` {.used.} = 1306 when bool(`isVO`): ctx.put(startMapEvent()) 1307 ctx.put(scalarEvent( 1308 `name`, 1309 if ctx.emitTag: yTagNimField else: yTagQuestionMark, 1310 yAnchorNone 1311 )) 1312 when `childAccessor`.hasCustomPragma(scalar): 1313 ctx.overridingScalarStyle = `childAccessor`.getCustomPragmaVal(scalar) 1314 when `childAccessor`.hasCustomPragma(collection): 1315 ctx.overridingCollectionStyle = `childAccessor`.getCustomPragmaVal(collection) 1316 ctx.representChild(`childAccessor`) 1317 when bool(`isVO`): ctx.put(endMapEvent()) 1318 when not `childAccessor`.hasCustomPragma(transient): 1319 when hasSparse(`tDecl`) and `child` is Option: 1320 if `childAccessor`.isSome: `templName`() 1321 else: 1322 `templName`() 1323 ) 1324 inc(fieldIndex) 1325 1326 macro genRepresentObject(t: typedesc, value) = 1327 result = newStmtList() 1328 let 1329 tDecl = getType(t) 1330 isVO = isVariantObject(t) 1331 var fieldIndex = 0'i16 1332 recGenFieldRepresenters(tDecl, value, isVO, fieldIndex, result) 1333 1334 proc representObject*[O: object]( 1335 ctx : var SerializationContext, 1336 value: O, 1337 tag : Tag, 1338 ) {.raises: [YamlSerializationError].} = 1339 ## represents a Nim object or tuple as YAML mapping 1340 when isVariantObject(getType(O)): 1341 ctx.put(startSeqEvent(tag = tag, style = ctx.collectionStyleFor(O))) 1342 else: 1343 ctx.put(startMapEvent(tag = tag, style = ctx.collectionStyleFor(O))) 1344 genRepresentObject(O, value) 1345 when isVariantObject(getType(O)): ctx.put(endSeqEvent()) 1346 else: ctx.put(endMapEvent()) 1347 1348 proc representObject*[O: tuple]( 1349 ctx : var SerializationContext, 1350 value: O, 1351 tag : Tag, 1352 ) {.raises: [YamlSerializationError].} = 1353 var fieldIndex = 0'i16 1354 ctx.put(startMapEvent(tag = tag, style = ctx.collectionStyleFor(O))) 1355 for name, fvalue in fieldPairs(value): 1356 ctx.put(scalarEvent( 1357 name, 1358 tag = if ctx.emitTag: yTagNimField else: yTagQuestionMark 1359 )) 1360 ctx.representChild(fvalue) 1361 inc(fieldIndex) 1362 ctx.put(endMapEvent()) 1363 1364 proc constructObject*[O: enum]( 1365 ctx : var ConstructionContext, 1366 result: var O, 1367 ) {.raises: [YamlConstructionError, YamlStreamError].} = 1368 ## constructs a Nim enum from a YAML scalar 1369 let e = ctx.input.next() 1370 if e.kind != yamlScalar: 1371 raise ctx.input.constructionError( 1372 e.startPos, "Expected scalar, got " & $e.kind 1373 ) 1374 try: result = parseEnum[O](e.scalarContent) 1375 except ValueError as ve: 1376 var ex = ctx.input.constructionError(e.startPos, "Cannot parse '" & 1377 escape(e.scalarContent) & "' as " & type(O).name 1378 ) 1379 ex.parent = ve 1380 raise ex 1381 1382 proc representObject*[O: enum]( 1383 ctx : var SerializationContext, 1384 value: O, 1385 tag : Tag, 1386 ) {.raises: [].} = 1387 ## represents a Nim enum as YAML scalar 1388 ctx.put(scalarEvent($value, tag, yAnchorNone, ctx.scalarStyleFor(O))) 1389 1390 proc yamlTag*[O](T: typedesc[ref O]): Tag {.inline, raises: [].} = yamlTag(O) 1391 1392 macro constructImplicitVariantObject( 1393 m, c, r, possibleTags: untyped, 1394 t: typedesc, 1395 ) = 1396 let tDesc = getType(getType(t)[1]) 1397 yAssert tDesc.kind == nnkObjectTy 1398 let recCase = tDesc[2][0] 1399 yAssert recCase.kind == nnkRecCase 1400 result = newNimNode(nnkIfStmt) 1401 for i in 1 .. recCase.len - 1: 1402 yAssert recCase[i].kind == nnkOfBranch 1403 var branch = newNimNode(nnkElifBranch) 1404 var branchContent = newStmtList(newAssignment(r, 1405 newNimNode(nnkObjConstr).add( 1406 newCall("type", r), 1407 newColonExpr(newIdentNode($recCase[0]), recCase[i][0]) 1408 ))) 1409 case recCase[i][1].recListLen 1410 of 0: 1411 branch.add(infix(newIdentNode("yTagNull"), "in", possibleTags)) 1412 branchContent.add( 1413 newNimNode(nnkDiscardStmt).add(newCall("next", input(c)))) 1414 of 1: 1415 let field = newDotExpr(r, newIdentNode($recCase[i][1].recListNode)) 1416 branch.add(infix( 1417 newCall("yamlTag", newCall("type", field)), "in", possibleTags)) 1418 branchContent.add(newCall("constructChild", c, field)) 1419 else: 1420 block: 1421 internalError("Too many children: " & $recCase[i][1].recListlen) 1422 branch.add(branchContent) 1423 result.add(branch) 1424 let raiseStmt = newNimNode(nnkRaiseStmt).add( 1425 newCall(bindSym("constructionError"), input(c), m, 1426 infix(newStrLitNode("This value type does not map to any field in " & 1427 getTypeImpl(t)[1].repr & ": "), "&", 1428 newCall("$", newNimNode(nnkBracketExpr).add(possibleTags, newIntLitNode(0))) 1429 ) 1430 )) 1431 result.add(newNimNode(nnkElse).add(newNimNode(nnkTryStmt).add( 1432 newStmtList(raiseStmt), newNimNode(nnkExceptBranch).add( 1433 newIdentNode("KeyError"), 1434 newNimNode(nnkDiscardStmt).add(newEmptyNode()) 1435 )))) 1436 1437 proc isImplicitVariantObject(t: typedesc): bool {.compileTime.} = 1438 when compiles(t.hasCustomPragma(implicit)): 1439 return t.hasCustomPragma(implicit) 1440 else: 1441 return false 1442 1443 proc canBeImplicit(t: typedesc): string {.compileTime.} = 1444 ## returns empty string if type can be implicit, else the reason why it can't 1445 let tDesc = getTypedescType(t) 1446 if tDesc.kind != nnkObjectTy: return "type is not an object" 1447 if tDesc[2].len != 1 or tDesc[2][0].kind != nnkRecCase: 1448 return "type doesn't exclusively contain record case" 1449 var foundEmptyBranch = false 1450 for i in 1.. tDesc[2][0].len - 1: 1451 case tDesc[2][0][i][1].recListlen # branch contents 1452 of 0: 1453 if foundEmptyBranch: return "record case has more than one empty branch" 1454 else: foundEmptyBranch = true 1455 of 1: discard 1456 else: return "record case contains more than one field" 1457 1458 proc constructChild*[T]( 1459 ctx : var ConstructionContext, 1460 result: var T, 1461 ) = 1462 let item = ctx.input.peek() 1463 when isImplicitVariantObject(T): 1464 const checkRes = canBeImplicit(T) 1465 when len(checkRes) > 0: 1466 {. fatal: "cannot be marked as implicit: " & checkRes .} 1467 var possibleTags = newSeq[Tag]() 1468 case item.kind 1469 of yamlScalar: 1470 case item.scalarProperties.tag 1471 of yTagQuestionMark: 1472 case guessType(item.scalarContent) 1473 of yTypeInteger: 1474 possibleTags.add([yamlTag(int), yamlTag(int8), yamlTag(int16), 1475 yamlTag(int32), yamlTag(int64)]) 1476 if item.scalarContent[0] != '-': 1477 possibleTags.add([yamlTag(uint), yamlTag(uint8), yamlTag(uint16), 1478 yamlTag(uint32), yamlTag(uint64)]) 1479 of yTypeFloat, yTypeFloatInf, yTypeFloatNaN: 1480 possibleTags.add([yamlTag(float), yamlTag(float32), 1481 yamlTag(float64)]) 1482 of yTypeBoolTrue, yTypeBoolFalse: 1483 possibleTags.add(yamlTag(bool)) 1484 of yTypeNull: 1485 raise ctx.input.constructionError(item.startPos, "not implemented!") 1486 of yTypeUnknown: 1487 possibleTags.add(yamlTag(string)) 1488 of yTypeTimestamp: 1489 possibleTags.add(yamlTag(Time)) 1490 of yTagExclamationMark: 1491 possibleTags.add(yamlTag(string)) 1492 else: 1493 possibleTags.add(item.scalarProperties.tag) 1494 of yamlStartMap: 1495 if item.mapProperties.tag in [yTagQuestionMark, yTagExclamationMark]: 1496 raise ctx.input.constructionError(item.startPos, 1497 "Complex value of implicit variant object type must have a tag.") 1498 possibleTags.add(item.mapProperties.tag) 1499 of yamlStartSeq: 1500 if item.seqProperties.tag in [yTagQuestionMark, yTagExclamationMark]: 1501 raise ctx.input.constructionError(item.startPos, 1502 "Complex value of implicit variant object type must have a tag.") 1503 possibleTags.add(item.seqProperties.tag) 1504 of yamlAlias: 1505 raise ctx.input.constructionError(item.startPos, 1506 "cannot load non-ref value from alias node") 1507 else: internalError("Unexpected item kind: " & $item.kind) 1508 constructImplicitVariantObject(item.startPos, ctx, result, possibleTags, T) 1509 else: 1510 case item.kind 1511 of yamlScalar: 1512 if item.scalarProperties.tag notin [yTagQuestionMark, yTagExclamationMark, 1513 yamlTag(T)]: 1514 raise ctx.input.constructionError( 1515 item.startPos, "Wrong tag for " & typetraits.name(T) & ": " & $item.scalarProperties.tag) 1516 elif item.scalarProperties.anchor != yAnchorNone: 1517 raise ctx.input.constructionError(item.startPos, "Anchor on non-ref type") 1518 of yamlStartMap: 1519 if item.mapProperties.tag notin [yTagQuestionMark, yamlTag(T)]: 1520 raise ctx.input.constructionError( 1521 item.startPos, "Wrong tag for " & typetraits.name(T) & ": " & $item.mapProperties.tag) 1522 elif item.mapProperties.anchor != yAnchorNone: 1523 raise ctx.input.constructionError(item.startPos, "Anchor on non-ref type") 1524 of yamlStartSeq: 1525 if item.seqProperties.tag notin [yTagQuestionMark, yamlTag(T)]: 1526 raise ctx.input.constructionError( 1527 item.startPos, "Wrong tag for " & typetraits.name(T) & ": " & $item.seqProperties.tag) 1528 elif item.seqProperties.anchor != yAnchorNone: 1529 raise ctx.input.constructionError(item.startPos, "Anchor on non-ref type") 1530 of yamlAlias: 1531 raise ctx.input.constructionError(item.startPos, 1532 "cannot load non-ref value from alias node") 1533 else: internalError("Unexpected item kind: " & $item.kind) 1534 ctx.constructObject(result) 1535 1536 proc constructChild*( 1537 ctx : var ConstructionContext, 1538 result: var string, 1539 ) = 1540 let item = ctx.input.peek() 1541 if item.kind == yamlScalar: 1542 if item.scalarProperties.tag notin 1543 [yTagQuestionMark, yTagExclamationMark, yamlTag(string)]: 1544 raise ctx.input.constructionError( 1545 item.startPos, "Wrong tag for string: " & $item.scalarProperties.tag) 1546 elif item.scalarProperties.anchor != yAnchorNone: 1547 raise ctx.input.constructionError(item.startPos, "Anchor on non-ref type") 1548 ctx.constructObject(result) 1549 1550 proc constructChild*[T]( 1551 ctx : var ConstructionContext, 1552 result: var seq[T], 1553 ) = 1554 let item = ctx.input.peek() 1555 if item.kind == yamlStartSeq: 1556 if item.seqProperties.tag notin [yTagQuestionMark, yamlTag(seq[T])]: 1557 raise ctx.input.constructionError( 1558 item.startPos, "Wrong tag for " & typetraits.name(seq[T]) & ": " & $item.seqProperties.tag) 1559 elif item.seqProperties.anchor != yAnchorNone: 1560 raise ctx.input.constructionError(item.startPos, "Anchor on non-ref type") 1561 ctx.constructObject(result) 1562 1563 proc constructChild*[I, T]( 1564 ctx : var ConstructionContext, 1565 result: var array[I, T], 1566 ) = 1567 let item = ctx.input.peek() 1568 if item.kind == yamlStartSeq: 1569 if item.seqProperties.tag notin [yTagQuestionMark, yamlTag(array[I, T])]: 1570 raise ctx.input.constructionError( 1571 item.startPos, "Wrong tag for " & typetraits.name(array[I, T]) & ": " & $item.seqProperties.tag) 1572 elif item.seqProperties.anchor != yAnchorNone: 1573 raise ctx.input.constructionError(item.startPos, "Anchor on non-ref type") 1574 ctx.constructObject(result) 1575 1576 proc constructChild*[T]( 1577 ctx : var ConstructionContext, 1578 result: var Option[T], 1579 ) = 1580 ## constructs an optional value. A value with a !!null tag will be loaded 1581 ## an empty value. 1582 let event = ctx.input.peek() 1583 if event.kind == yamlScalar and event.scalarProperties.tag == yTagNull: 1584 result = none(T) 1585 discard ctx.input.next() 1586 else: 1587 var inner: T 1588 ctx.constructChild(inner) 1589 result = some(inner) 1590 1591 when defined(JS): 1592 # in JS, Time is a ref type. Therefore, we need this specialization so that 1593 # it is not handled by the general ref-type handler. 1594 proc constructChild*( 1595 ctx : var ConstructionContext, 1596 result: var Time, 1597 ) = 1598 let e = ctx.input.peek() 1599 if e.kind == yamlScalar: 1600 if e.scalarProperties.tag notin [yTagQuestionMark, yTagTimestamp]: 1601 raise ctx.input.constructionError(e.startPos, "Wrong tag for Time: " & $e.scalarProperties.tag) 1602 elif guessType(e.scalarContent) != yTypeTimestamp: 1603 raise ctx.input.constructionError(e.startPos, "Invalid timestamp") 1604 elif e.scalarProperties.anchor != yAnchorNone: 1605 raise ctx.input.constructionError(e.startPos, "Anchor on non-ref type") 1606 ctx.constructObject(result) 1607 else: 1608 raise ctx.input.constructionError(e.startPos, "Unexpected structure, expected timestamp") 1609 1610 proc constructChild*[O]( 1611 ctx : var ConstructionContext, 1612 result: var ref O, 1613 ) = 1614 var e = ctx.input.peek() 1615 if e.kind == yamlScalar: 1616 let props = e.scalarProperties 1617 if props.tag == yTagNull or (props.tag == yTagQuestionMark and 1618 guessType(e.scalarContent) == yTypeNull): 1619 result = nil 1620 discard ctx.input.next() 1621 return 1622 elif e.kind == yamlAlias: 1623 when nimvm: 1624 raise ctx.input.constructionError(e.startPos, 1625 "aliases are not supported at compile time") 1626 else: 1627 let val = ctx.refs.getOrDefault(e.aliasTarget, (yTagNull, pointer(nil))) 1628 if val.p == nil: 1629 raise ctx.input.constructionError(e.startPos, 1630 "alias node refers to anchor in ignored scope") 1631 if val.tag != yamlTag(O): 1632 raise ctx.input.constructionError(e.startPos, 1633 "alias node refers to object of incompatible type") 1634 result = cast[ref O](val.p) 1635 discard ctx.input.next() 1636 return 1637 new(result) 1638 template removeAnchor(anchor: var Anchor) {.dirty.} = 1639 if anchor != yAnchorNone: 1640 yAssert(not ctx.refs.hasKey(anchor)) 1641 when nimvm: discard # no aliases supported at compile time 1642 else: ctx.refs[anchor] = (yamlTag(O), cast[pointer](result)) 1643 anchor = yAnchorNone 1644 1645 case e.kind 1646 of yamlScalar: removeAnchor(e.scalarProperties.anchor) 1647 of yamlStartMap: removeAnchor(e.mapProperties.anchor) 1648 of yamlStartSeq: removeAnchor(e.seqProperties.anchor) 1649 else: internalError("Unexpected event kind: " & $e.kind) 1650 ctx.input.peek = e 1651 try: ctx.constructChild(result[]) 1652 except YamlConstructionError as e: raise e 1653 except YamlStreamError as e: raise e 1654 except CatchableError as ce: 1655 var e = newException(YamlStreamError, ce.msg) 1656 e.parent = ce 1657 raise e 1658 1659 proc representChild*( 1660 ctx : var SerializationContext, 1661 value: string, 1662 ) = 1663 let tag = ctx.presentTag(string) 1664 ctx.representObject( 1665 value, 1666 if tag == yTagQuestionMark and guessType(value) != yTypeUnknown: 1667 yTagExclamationMark 1668 else: 1669 tag 1670 ) 1671 1672 proc representChild*[T]( 1673 ctx : var SerializationContext, 1674 value: seq[T], 1675 ) {.raises: [YamlSerializationError].} = 1676 ctx.representObject(value, ctx.presentTag(seq[T])) 1677 1678 proc representChild*[I, T]( 1679 ctx : var SerializationContext, 1680 value: array[I, T], 1681 ) {.raises: [YamlSerializationError].} = 1682 ctx.representObject(value, ctx.presentTag(array[I, T])) 1683 1684 proc representChild*[O]( 1685 ctx : var SerializationContext, 1686 value: ref O, 1687 ) = 1688 if isNil(value): ctx.put(scalarEvent("~", yTagNull, style = ctx.scalarStyleFor(O))) 1689 else: 1690 when nimvm: discard 1691 else: 1692 let p = cast[pointer](value) 1693 # when c.anchorStyle == asNone, `referenced` is used as indicator that we are 1694 # currently in the process of serializing this node. This enables us to 1695 # detect cycles and raise an error. 1696 var val = ctx.refs.getOrDefault( 1697 p, (ctx.nextAnchorId.Anchor, ctx.options.anchorStyle == asNone) 1698 ) 1699 if val.a != ctx.nextAnchorId.Anchor: 1700 if ctx.options.anchorStyle == asNone: 1701 if val.referenced: 1702 raise newException(YamlSerializationError, 1703 "tried to serialize cyclic graph with asNone") 1704 else: 1705 val = ctx.refs.getOrDefault(p) 1706 yAssert(val.a != yAnchorNone) 1707 if not val.referenced: 1708 ctx.refs[p] = (val.a, true) 1709 ctx.put(aliasEvent(val.a)) 1710 ctx.overridingScalarStyle = ssAny 1711 ctx.overridingCollectionStyle = csAny 1712 return 1713 ctx.refs[p] = val 1714 nextAnchor(ctx.nextAnchorId, len(ctx.nextAnchorId) - 1) 1715 let origPut = ctx.putImpl 1716 ctx.putImpl = proc(ctx: var SerializationContext, e: Event) = 1717 var ex = e 1718 case ex.kind 1719 of yamlStartMap: 1720 if ctx.options.anchorStyle != asNone: ex.mapProperties.anchor = val.a 1721 of yamlStartSeq: 1722 if ctx.options.anchorStyle != asNone: ex.seqProperties.anchor = val.a 1723 of yamlScalar: 1724 if ctx.options.anchorStyle != asNone: ex.scalarProperties.anchor = val.a 1725 if not ctx.emitTag and guessType(ex.scalarContent) != yTypeNull: 1726 ex.scalarProperties.tag = yTagQuestionMark 1727 else: discard 1728 ctx.putImpl = origPut 1729 ctx.put(ex) 1730 ctx.representChild(value[]) 1731 when nimvm: discard 1732 else: 1733 if ctx.options.anchorStyle == asNone: ctx.refs[p] = (val.a, false) 1734 1735 proc representChild*[T]( 1736 ctx : var SerializationContext, 1737 value: Option[T], 1738 ) {.raises: [YamlSerializationError].} = 1739 ## represents an optional value. If the value is missing, a !!null scalar 1740 ## will be produced. 1741 if value.isSome: 1742 ctx.representChild(value.get()) 1743 else: 1744 ctx.put(scalarEvent("~", yTagNull, style = ctx.scalarStyleFor(Option[T]))) 1745 1746 proc representChild*[O]( 1747 ctx : var SerializationContext, 1748 value: O, 1749 ) = 1750 when isImplicitVariantObject(O): 1751 # todo: this would probably be nicer if constructed with a macro 1752 var count = 0 1753 for name, field in fieldPairs(value): 1754 if count > 0: ctx.representChild(field) 1755 inc(count) 1756 if count == 1: ctx.put(scalarEvent("~", yTagNull)) 1757 else: 1758 ctx.representObject(value, ctx.presentTag(O)) 1759 1760 proc construct*[T]( 1761 input : var YamlStream, 1762 target: var T, 1763 ) 1764 {.raises: [YamlStreamError, YamlConstructionError].} = 1765 ## Constructs a Nim value from a YAML stream. 1766 var context = initConstructionContext(input) 1767 try: 1768 var e = input.next() 1769 yAssert(e.kind == yamlStartDoc) 1770 1771 context.constructChild(target) 1772 e = input.next() 1773 yAssert(e.kind == yamlEndDoc) 1774 except YamlConstructionError as e: raise e 1775 except YamlStreamError as e: raise e 1776 except CatchableError as ce: 1777 # may occur while calling ctx.input() 1778 var ex = newException(YamlStreamError, "error occurred while constructing") 1779 ex.parent = ce 1780 raise ex 1781 1782 proc represent*[T]( 1783 value : T, 1784 options: SerializationOptions = SerializationOptions(), 1785 ): YamlStream = 1786 ## Represents a Nim value as ``YamlStream`` 1787 var 1788 bys = newBufferYamlStream() 1789 context = initSerializationContext( 1790 options, 1791 proc(ctx: var SerializationContext, e: Event) = bys.put(e) 1792 ) 1793 bys.put(startStreamEvent()) 1794 bys.put(startDocEvent(handles = options.handles)) 1795 context.representChild(value) 1796 bys.put(endDocEvent()) 1797 bys.put(endStreamEvent()) 1798 if options.anchorStyle == asTidy: 1799 var ctx = initAnchorContext() 1800 for item in bys.mitems(): 1801 case item.kind 1802 of yamlStartMap: ctx.process(item.mapProperties, context.refs) 1803 of yamlStartSeq: ctx.process(item.seqProperties, context.refs) 1804 of yamlScalar: ctx.process(item.scalarProperties, context.refs) 1805 of yamlAlias: item.aliasTarget = ctx.map(item.aliasTarget) 1806 else: discard 1807 result = bys 1808