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