summary refs log tree commit diff stats
path: root/lib/pure/streams.nim
blob: 62cb385d48175799e1b8cab2a4f5835b33c4d39c (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
#
#
#            Nimrod's Runtime Library
#        (c) Copyright 2011 Andreas Rumpf
#
#    See the file "copying.txt", included in this
#    distribution, for details about the copyright.
#

## This module provides a stream interface and two implementations thereof:
## the `PFileStream` and the `PStringStream` which implement the stream
## interface for Nimrod file objects (`TFile`) and strings. Other modules
## may provide other implementations for this standard stream interface.

proc newEIO(msg: string): ref EIO =
  new(result)
  result.msg = msg

type
  PStream* = ref TStream
  TStream* = object of TObject ## Stream interface that supports
                               ## writing or reading.
    close*: proc (s: PStream)
    atEnd*: proc (s: PStream): bool
    setPosition*: proc (s: PStream, pos: int)
    getPosition*: proc (s: PStream): int
    readData*: proc (s: PStream, buffer: pointer, bufLen: int): int
    writeData*: proc (s: PStream, buffer: pointer, bufLen: int)

proc write*[T](s: PStream, x: T) = 
  ## generic write procedure. Writes `x` to the stream `s`. Implementation:
  ##
  ## .. code-block:: Nimrod
  ##
  ##     s.writeData(s, addr(x), sizeof(x))
  var y: T
  shallowCopy(y, x)
  s.writeData(s, addr(y), sizeof(y))

proc write*(s: PStream, x: string) = 
  ## writes the string `x` to the the stream `s`. No length field or 
  ## terminating zero is written.
  s.writeData(s, cstring(x), x.len)

proc read[T](s: PStream, result: var T) = 
  ## generic read procedure. Reads `result` from the stream `s`.
  if s.readData(s, addr(result), sizeof(T)) != sizeof(T):
    raise newEIO("cannot read from stream")

proc readChar*(s: PStream): char =
  ## reads a char from the stream `s`. Raises `EIO` if an error occured.
  ## Returns '\0' as an EOF marker.
  discard s.readData(s, addr(result), sizeof(result))

proc readBool*(s: PStream): bool = 
  ## reads a bool from the stream `s`. Raises `EIO` if an error occured.
  read(s, result)

proc readInt8*(s: PStream): int8 = 
  ## reads an int8 from the stream `s`. Raises `EIO` if an error occured.
  read(s, result)

proc readInt16*(s: PStream): int16 = 
  ## reads an int16 from the stream `s`. Raises `EIO` if an error occured.
  read(s, result)

proc readInt32*(s: PStream): int32 = 
  ## reads an int32 from the stream `s`. Raises `EIO` if an error occured.
  read(s, result)

proc readInt64*(s: PStream): int64 = 
  ## reads an int64 from the stream `s`. Raises `EIO` if an error occured.
  read(s, result)

proc readFloat32*(s: PStream): float32 = 
  ## reads a float32 from the stream `s`. Raises `EIO` if an error occured.
  read(s, result)

proc readFloat64*(s: PStream): float64 = 
  ## reads a float64 from the stream `s`. Raises `EIO` if an error occured.
  read(s, result)

proc readStr*(s: PStream, length: int): string = 
  ## reads a string of length `length` from the stream `s`. Raises `EIO` if 
  ## an error occured.
  result = newString(length)
  var L = s.readData(s, addr(result[0]), length)
  if L != length: setLen(result, L)

proc readLine*(s: PStream): string =
  ## Reads a line from a stream `s`. Note: This is not very efficient. Raises 
  ## `EIO` if an error occured.
  result = ""
  while not s.atEnd(s): 
    var c = readChar(s)
    if c == '\c': 
      c = readChar(s)
      break
    elif c == '\L' or c == '\0': break
    result.add(c)

type
  PStringStream* = ref TStringStream ## a stream that encapsulates a string
  TStringStream* = object of TStream
    data*: string
    pos: int
    
proc ssAtEnd(s: PStream): bool = 
  var s = PStringStream(s)
  return s.pos >= s.data.len
    
proc ssSetPosition(s: PStream, pos: int) = 
  var s = PStringStream(s)
  s.pos = min(pos, s.data.len-1)

proc ssGetPosition(s: PStream): int =
  var s = PStringStream(s)
  return s.pos

proc ssReadData(s: PStream, buffer: pointer, bufLen: int): int =
  var s = PStringStream(s)
  result = min(bufLen, s.data.len - s.pos)
  if result > 0: 
    copyMem(buffer, addr(s.data[s.pos]), result)
    inc(s.pos, result)

proc ssWriteData(s: PStream, buffer: pointer, bufLen: int) = 
  var s = PStringStream(s)
  if bufLen > 0: 
    setLen(s.data, s.data.len + bufLen)
    copyMem(addr(s.data[s.pos]), buffer, bufLen)
    inc(s.pos, bufLen)

proc ssClose(s: PStream) =
  var s = PStringStream(s)
  s.data = nil

proc newStringStream*(s: string = ""): PStringStream = 
  ## creates a new stream from the string `s`.
  new(result)
  result.data = s
  result.pos = 0
  result.close = ssClose
  result.atEnd = ssAtEnd
  result.setPosition = ssSetPosition
  result.getPosition = ssGetPosition
  result.readData = ssReadData
  result.writeData = ssWriteData

type
  PFileStream* = ref TFileStream ## a stream that encapsulates a `TFile`
  TFileStream* = object of TStream
    f: TFile

proc fsClose(s: PStream) = close(PFileStream(s).f)
proc fsAtEnd(s: PStream): bool = return EndOfFile(PFileStream(s).f)
proc fsSetPosition(s: PStream, pos: int) = setFilePos(PFileStream(s).f, pos)
proc fsGetPosition(s: PStream): int = return int(getFilePos(PFileStream(s).f))

proc fsReadData(s: PStream, buffer: pointer, bufLen: int): int = 
  result = readBuffer(PFileStream(s).f, buffer, bufLen)
  
proc fsWriteData(s: PStream, buffer: pointer, bufLen: int) = 
  if writeBuffer(PFileStream(s).f, buffer, bufLen) != bufLen: 
    raise newEIO("cannot write to stream")

proc newFileStream*(f: TFile): PFileStream = 
  ## creates a new stream from the file `f`.
  new(result)
  result.f = f
  result.close = fsClose
  result.atEnd = fsAtEnd
  result.setPosition = fsSetPosition
  result.getPosition = fsGetPosition
  result.readData = fsReadData
  result.writeData = fsWriteData

proc newFileStream*(filename: string, mode: TFileMode): PFileStream = 
  ## creates a new stream from the file named `filename` with the mode `mode`.
  ## If the file cannot be opened, nil is returned.
  var f: TFile
  if Open(f, filename, mode): result = newFileStream(f)


when true:
  nil
else:
  type
    TFileHandle* = cint ## Operating system file handle
    PFileHandleStream* = ref TFileHandleStream
    TFileHandleStream* = object of TStream
      handle*: TFileHandle
      pos: int

  proc newEOS(msg: string): ref EOS =
    new(result)
    result.msg = msg

  proc hsGetPosition(s: PFileHandleStream): int = 
    return s.pos

  when defined(windows):
    # do not import windows as this increases compile times:
    nil
  else:
    import posix
    
    proc hsSetPosition(s: PFileHandleStream, pos: int) = 
      discard lseek(s.handle, pos, SEEK_SET)

    proc hsClose(s: PFileHandleStream) = discard close(s.handle)
    proc hsAtEnd(s: PFileHandleStream): bool = 
      var pos = hsGetPosition(s)
      var theEnd = lseek(s.handle, 0, SEEK_END)
      result = pos >= theEnd
      hsSetPosition(s, pos) # set position back

    proc hsReadData(s: PFileHandleStream, buffer: pointer, bufLen: int): int = 
      result = posix.read(s.handle, buffer, bufLen)
      inc(s.pos, result)
      
    proc hsWriteData(s: PFileHandleStream, buffer: pointer, bufLen: int) = 
      if posix.write(s.handle, buffer, bufLen) != bufLen: 
        raise newEIO("cannot write to stream")
      inc(s.pos, bufLen)

  proc newFileHandleStream*(handle: TFileHandle): PFileHandleStream = 
    new(result)
    result.handle = handle
    result.pos = 0
    result.close = hsClose
    result.atEnd = hsAtEnd
    result.setPosition = hsSetPosition
    result.getPosition = hsGetPosition
    result.readData = hsReadData
    result.writeData = hsWriteData

  proc newFileHandleStream*(filename: string, 
                            mode: TFileMode): PFileHandleStream = 
    when defined(windows): 
      nil
    else:
      var flags: cint
      case mode
      of fmRead:              flags = posix.O_RDONLY
      of fmWrite:             flags = O_WRONLY or int(O_CREAT)
      of fmReadWrite:         flags = O_RDWR or int(O_CREAT)
      of fmReadWriteExisting: flags = O_RDWR
      of fmAppend:            flags = O_WRONLY or int(O_CREAT) or O_APPEND
      var handle = open(filename, flags)
      if handle < 0: raise newEOS("posix.open() call failed")
    result = newFileHandleStream(handle)