/ yaml / stream.nim
stream.nim
  1  #            NimYAML - YAML implementation in Nim
  2  #        (c) Copyright 2015-2023 Felix Krause
  3  #
  4  #    See the file "copying.txt", included in this
  5  #    distribution, for details about the copyright.
  6  
  7  ## ==================
  8  ## Module yaml/stream
  9  ## ==================
 10  ##
 11  ## The stream API provides the basic data structure ``YamlStream`` on which all
 12  ## low-level APIs operate. It is not named ``streams`` to not confuse it with
 13  ## the module in the stdlib with that name.
 14  
 15  import data
 16  
 17  when defined(nimNoNil):
 18      {.experimental: "notnil".}
 19  
 20  type
 21    YamlStreamError* = object of ValueError
 22      ## Exception that may be raised by a ``YamlStream`` when the underlying
 23      ## backend raises an exception. The error that has occurred is
 24      ## available from ``parent``.
 25  
 26    YamlStream* = ref object of RootObj ## \
 27      ## A ``YamlStream`` is an iterator-like object that yields a
 28      ## well-formed stream of ``YamlStreamEvents``. Well-formed means that
 29      ## every ``yamlStartMap`` is terminated by a ``yamlEndMap``, every
 30      ## ``yamlStartSeq`` is terminated by a ``yamlEndSeq`` and every
 31      ## ``yamlStartDoc`` is terminated by a ``yamlEndDoc``. Moreover, every
 32      ## emitted mapping has an even number of children.
 33      ##
 34      ## The creator of a ``YamlStream`` is responsible for it being
 35      ## well-formed. A user of the stream may assume that it is well-formed
 36      ## and is not required to check for it. The procs in this module will
 37      ## always yield a well-formed ``YamlStream`` and expect it to be
 38      ## well-formed if they take it as input parameter.
 39      nextImpl*: proc(s: YamlStream, e: var Event): bool {.gcSafe, raises: [CatchableError] .}
 40      lastTokenContextImpl*:
 41          proc(s: YamlStream, lineContent: var string): bool {.raises: [].}
 42      peeked: bool
 43      cached: Event
 44  
 45  proc noLastContext(s: YamlStream, lineContent: var string): bool {.raises: [].} =
 46    result = false
 47  
 48  proc basicInit*(s: YamlStream, lastTokenContextImpl:
 49      proc(s: YamlStream, lineContent: var string): bool
 50      {.raises: [].} = noLastContext) {.raises: [].} =
 51    ## initialize basic values of the YamlStream. Call this in your constructor
 52    ## if you subclass YamlStream.
 53    s.peeked = false
 54    s.lastTokenContextImpl = lastTokenContextImpl
 55  
 56  when not defined(JS):
 57    type IteratorYamlStream = ref object of YamlStream
 58      backend: iterator(): Event {.gcSafe, raises: [CatchableError].}
 59  
 60    proc initYamlStream*(
 61      backend: iterator(): Event {.gcSafe, raises: [CatchableError].}
 62    ): YamlStream {.raises: [].} =
 63      ## Creates a new ``YamlStream`` that uses the given iterator as backend.
 64      result = new(IteratorYamlStream)
 65      result.basicInit()
 66      IteratorYamlStream(result).backend = backend
 67      result.nextImpl = proc(s: YamlStream, e: var Event): bool {.gcSafe, raises: [CatchableError].} =
 68        e = IteratorYamlStream(s).backend()
 69        result = true
 70  
 71  type
 72    BufferYamlStream* = ref object of YamlStream
 73      pos: int
 74      buf: seq[Event]
 75  
 76  proc newBufferYamlStream*(): BufferYamlStream =
 77    result = new(BufferYamlStream)
 78    result.basicInit()
 79    result.buf = @[]
 80    result.pos = 0
 81    result.nextImpl = proc(s: YamlStream, e: var Event): bool =
 82      let bys = BufferYamlStream(s)
 83      e = bys.buf[bys.pos]
 84      inc(bys.pos)
 85      result = true
 86  
 87  proc put*(bys: BufferYamlStream, e: Event) {.raises: [].} =
 88    bys.buf.add(e)
 89  
 90  proc next*(s: YamlStream): Event {.raises: [YamlStreamError], gcSafe.} =
 91    ## Get the next item of the stream. Requires ``finished(s) == true``.
 92    ## If the backend yields an exception, that exception will be encapsulated
 93    ## into a ``YamlStreamError``, which will be raised.
 94    if s.peeked:
 95      s.peeked = false
 96      return move(s.cached)
 97    else:
 98      try:
 99        while true:
100          if s.nextImpl(s, result): break
101      except YamlStreamError as e: raise e
102      except CatchableError as ce:
103        var e = newException(YamlStreamError, ce.msg)
104        e.parent = ce
105        raise e
106  
107  proc peek*(s: YamlStream): lent Event {.raises: [YamlStreamError].} =
108    ## Get the next item of the stream without advancing the stream.
109    ## Requires ``finished(s) == true``. Handles exceptions of the backend like
110    ## ``next()``.
111    if not s.peeked:
112      s.cached = s.next()
113      s.peeked = true
114    result = s.cached
115  
116  proc `peek=`*(s: YamlStream, value: Event) {.raises: [].} =
117    ## Set the next item of the stream. Will replace a previously peeked item,
118    ## if one exists.
119    s.cached = value
120    s.peeked = true
121  
122  proc getLastTokenContext*(s: YamlStream, lineContent: var string): bool =
123    ## ``true`` if source context information is available about the last returned
124    ## token. If ``true``, line, column and lineContent are set to position and
125    ## line content where the last token has been read from.
126    result = s.lastTokenContextImpl(s, lineContent)
127  
128  iterator items*(s: YamlStream): Event
129      {.raises: [YamlStreamError].} =
130    ## Iterate over all items of the stream. You may not use ``peek()`` on the
131    ## stream while iterating.
132    while true:
133      let e = s.next()
134      var last = e.kind == yamlEndStream
135      yield e
136      if last: break
137  
138  iterator mitems*(bys: BufferYamlStream): var Event {.raises: [].} =
139    ## Iterate over all items of the stream. You may not use ``peek()`` on the
140    ## stream while iterating.
141    for e in bys.buf.mitems(): yield e