streams.nim 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540
  1. #
  2. #
  3. # Nim's Runtime Library
  4. # (c) Copyright 2015 Andreas Rumpf
  5. #
  6. # See the file "copying.txt", included in this
  7. # distribution, for details about the copyright.
  8. #
  9. ## This module provides a stream interface and two implementations thereof:
  10. ## the `FileStream` and the `StringStream` which implement the stream
  11. ## interface for Nim file objects (`File`) and strings. Other modules
  12. ## may provide other implementations for this standard stream interface.
  13. ##
  14. ## Examples:
  15. ##
  16. ## .. code-block:: Nim
  17. ##
  18. ## import streams
  19. ## var
  20. ## ss = newStringStream("""The first line
  21. ## the second line
  22. ## the third line""")
  23. ## line = ""
  24. ## while ss.readLine(line):
  25. ## echo line
  26. ## ss.close()
  27. ##
  28. ## var fs = newFileStream("somefile.txt", fmRead)
  29. ## if not isNil(fs):
  30. ## while fs.readLine(line):
  31. ## echo line
  32. ## fs.close()
  33. include "system/inclrtl"
  34. proc newEIO(msg: string): ref IOError =
  35. new(result)
  36. result.msg = msg
  37. type
  38. Stream* = ref StreamObj
  39. StreamObj* = object of RootObj ## Stream interface that supports
  40. ## writing or reading. Note that these fields
  41. ## here shouldn't be used directly. They are
  42. ## accessible so that a stream implementation
  43. ## can override them.
  44. closeImpl*: proc (s: Stream) {.nimcall, tags: [], gcsafe.}
  45. atEndImpl*: proc (s: Stream): bool {.nimcall, tags: [], gcsafe.}
  46. setPositionImpl*: proc (s: Stream, pos: int) {.nimcall, tags: [], gcsafe.}
  47. getPositionImpl*: proc (s: Stream): int {.nimcall, tags: [], gcsafe.}
  48. readDataImpl*: proc (s: Stream, buffer: pointer,
  49. bufLen: int): int {.nimcall, tags: [ReadIOEffect], gcsafe.}
  50. peekDataImpl*: proc (s: Stream, buffer: pointer,
  51. bufLen: int): int {.nimcall, tags: [ReadIOEffect], gcsafe.}
  52. writeDataImpl*: proc (s: Stream, buffer: pointer, bufLen: int) {.nimcall,
  53. tags: [WriteIOEffect], gcsafe.}
  54. flushImpl*: proc (s: Stream) {.nimcall, tags: [WriteIOEffect], gcsafe.}
  55. proc flush*(s: Stream) =
  56. ## flushes the buffers that the stream `s` might use.
  57. if not isNil(s.flushImpl): s.flushImpl(s)
  58. proc close*(s: Stream) =
  59. ## closes the stream `s`.
  60. if not isNil(s.closeImpl): s.closeImpl(s)
  61. proc close*(s, unused: Stream) {.deprecated.} =
  62. ## closes the stream `s`.
  63. s.closeImpl(s)
  64. proc atEnd*(s: Stream): bool =
  65. ## checks if more data can be read from `f`. Returns true if all data has
  66. ## been read.
  67. result = s.atEndImpl(s)
  68. proc setPosition*(s: Stream, pos: int) =
  69. ## sets the position `pos` of the stream `s`.
  70. s.setPositionImpl(s, pos)
  71. proc getPosition*(s: Stream): int =
  72. ## retrieves the current position in the stream `s`.
  73. result = s.getPositionImpl(s)
  74. proc readData*(s: Stream, buffer: pointer, bufLen: int): int =
  75. ## low level proc that reads data into an untyped `buffer` of `bufLen` size.
  76. result = s.readDataImpl(s, buffer, bufLen)
  77. when not defined(js):
  78. proc readAll*(s: Stream): string =
  79. ## Reads all available data.
  80. const bufferSize = 1024
  81. var buffer {.noinit.}: array[bufferSize, char]
  82. while true:
  83. let readBytes = readData(s, addr(buffer[0]), bufferSize)
  84. if readBytes == 0:
  85. break
  86. let prevLen = result.len
  87. result.setLen(prevLen + readBytes)
  88. copyMem(addr(result[prevLen]), addr(buffer[0]), readBytes)
  89. if readBytes < bufferSize:
  90. break
  91. proc peekData*(s: Stream, buffer: pointer, bufLen: int): int =
  92. ## low level proc that reads data into an untyped `buffer` of `bufLen` size
  93. ## without moving stream position
  94. result = s.peekDataImpl(s, buffer, bufLen)
  95. proc writeData*(s: Stream, buffer: pointer, bufLen: int) =
  96. ## low level proc that writes an untyped `buffer` of `bufLen` size
  97. ## to the stream `s`.
  98. s.writeDataImpl(s, buffer, bufLen)
  99. proc writeData*(s, unused: Stream, buffer: pointer,
  100. bufLen: int) {.deprecated.} =
  101. ## low level proc that writes an untyped `buffer` of `bufLen` size
  102. ## to the stream `s`.
  103. s.writeDataImpl(s, buffer, bufLen)
  104. proc write*[T](s: Stream, x: T) =
  105. ## generic write procedure. Writes `x` to the stream `s`. Implementation:
  106. ##
  107. ## .. code-block:: Nim
  108. ##
  109. ## s.writeData(s, addr(x), sizeof(x))
  110. var y: T
  111. shallowCopy(y, x)
  112. writeData(s, addr(y), sizeof(y))
  113. proc write*(s: Stream, x: string) =
  114. ## writes the string `x` to the the stream `s`. No length field or
  115. ## terminating zero is written.
  116. when nimvm:
  117. writeData(s, cstring(x), x.len)
  118. else:
  119. if x.len > 0: writeData(s, cstring(x), x.len)
  120. proc write*(s: Stream, args: varargs[string, `$`]) =
  121. ## writes one or more strings to the the stream. No length fields or
  122. ## terminating zeros are written.
  123. for str in args: write(s, str)
  124. proc writeLine*(s: Stream, args: varargs[string, `$`]) =
  125. ## writes one or more strings to the the stream `s` followed
  126. ## by a new line. No length field or terminating zero is written.
  127. for str in args: write(s, str)
  128. write(s, "\n")
  129. proc read[T](s: Stream, result: var T) =
  130. ## generic read procedure. Reads `result` from the stream `s`.
  131. if readData(s, addr(result), sizeof(T)) != sizeof(T):
  132. raise newEIO("cannot read from stream")
  133. proc peek[T](s: Stream, result: var T) =
  134. ## generic peek procedure. Peeks `result` from the stream `s`.
  135. if peekData(s, addr(result), sizeof(T)) != sizeof(T):
  136. raise newEIO("cannot read from stream")
  137. proc readChar*(s: Stream): char =
  138. ## reads a char from the stream `s`. Raises `IOError` if an error occurred.
  139. ## Returns '\\0' as an EOF marker.
  140. if readData(s, addr(result), sizeof(result)) != 1: result = '\0'
  141. proc peekChar*(s: Stream): char =
  142. ## peeks a char from the stream `s`. Raises `IOError` if an error occurred.
  143. ## Returns '\\0' as an EOF marker.
  144. if peekData(s, addr(result), sizeof(result)) != 1: result = '\0'
  145. proc readBool*(s: Stream): bool =
  146. ## reads a bool from the stream `s`. Raises `IOError` if an error occurred.
  147. read(s, result)
  148. proc peekBool*(s: Stream): bool =
  149. ## peeks a bool from the stream `s`. Raises `IOError` if an error occurred.
  150. peek(s, result)
  151. proc readInt8*(s: Stream): int8 =
  152. ## reads an int8 from the stream `s`. Raises `IOError` if an error occurred.
  153. read(s, result)
  154. proc peekInt8*(s: Stream): int8 =
  155. ## peeks an int8 from the stream `s`. Raises `IOError` if an error occurred.
  156. peek(s, result)
  157. proc readInt16*(s: Stream): int16 =
  158. ## reads an int16 from the stream `s`. Raises `IOError` if an error occurred.
  159. read(s, result)
  160. proc peekInt16*(s: Stream): int16 =
  161. ## peeks an int16 from the stream `s`. Raises `IOError` if an error occurred.
  162. peek(s, result)
  163. proc readInt32*(s: Stream): int32 =
  164. ## reads an int32 from the stream `s`. Raises `IOError` if an error occurred.
  165. read(s, result)
  166. proc peekInt32*(s: Stream): int32 =
  167. ## peeks an int32 from the stream `s`. Raises `IOError` if an error occurred.
  168. peek(s, result)
  169. proc readInt64*(s: Stream): int64 =
  170. ## reads an int64 from the stream `s`. Raises `IOError` if an error occurred.
  171. read(s, result)
  172. proc peekInt64*(s: Stream): int64 =
  173. ## peeks an int64 from the stream `s`. Raises `IOError` if an error occurred.
  174. peek(s, result)
  175. proc readUint8*(s: Stream): uint8 =
  176. ## reads an uint8 from the stream `s`. Raises `IOError` if an error occurred.
  177. read(s, result)
  178. proc peekUint8*(s: Stream): uint8 =
  179. ## peeks an uint8 from the stream `s`. Raises `IOError` if an error occurred.
  180. peek(s, result)
  181. proc readUint16*(s: Stream): uint16 =
  182. ## reads an uint16 from the stream `s`. Raises `IOError` if an error occurred.
  183. read(s, result)
  184. proc peekUint16*(s: Stream): uint16 =
  185. ## peeks an uint16 from the stream `s`. Raises `IOError` if an error occurred.
  186. peek(s, result)
  187. proc readUint32*(s: Stream): uint32 =
  188. ## reads an uint32 from the stream `s`. Raises `IOError` if an error occurred.
  189. read(s, result)
  190. proc peekUint32*(s: Stream): uint32 =
  191. ## peeks an uint32 from the stream `s`. Raises `IOError` if an error occurred.
  192. peek(s, result)
  193. proc readUint64*(s: Stream): uint64 =
  194. ## reads an uint64 from the stream `s`. Raises `IOError` if an error occurred.
  195. read(s, result)
  196. proc peekUint64*(s: Stream): uint64 =
  197. ## peeks an uint64 from the stream `s`. Raises `IOError` if an error occurred.
  198. peek(s, result)
  199. proc readFloat32*(s: Stream): float32 =
  200. ## reads a float32 from the stream `s`. Raises `IOError` if an error occurred.
  201. read(s, result)
  202. proc peekFloat32*(s: Stream): float32 =
  203. ## peeks a float32 from the stream `s`. Raises `IOError` if an error occurred.
  204. peek(s, result)
  205. proc readFloat64*(s: Stream): float64 =
  206. ## reads a float64 from the stream `s`. Raises `IOError` if an error occurred.
  207. read(s, result)
  208. proc peekFloat64*(s: Stream): float64 =
  209. ## peeks a float64 from the stream `s`. Raises `IOError` if an error occurred.
  210. peek(s, result)
  211. proc readStr*(s: Stream, length: int): TaintedString =
  212. ## reads a string of length `length` from the stream `s`. Raises `IOError` if
  213. ## an error occurred.
  214. result = newString(length).TaintedString
  215. var L = readData(s, cstring(result), length)
  216. if L != length: setLen(result.string, L)
  217. proc peekStr*(s: Stream, length: int): TaintedString =
  218. ## peeks a string of length `length` from the stream `s`. Raises `IOError` if
  219. ## an error occurred.
  220. result = newString(length).TaintedString
  221. var L = peekData(s, cstring(result), length)
  222. if L != length: setLen(result.string, L)
  223. proc readLine*(s: Stream, line: var TaintedString): bool =
  224. ## reads a line of text from the stream `s` into `line`. `line` must not be
  225. ## ``nil``! May throw an IO exception.
  226. ## A line of text may be delimited by ```LF`` or ``CRLF``.
  227. ## The newline character(s) are not part of the returned string.
  228. ## Returns ``false`` if the end of the file has been reached, ``true``
  229. ## otherwise. If ``false`` is returned `line` contains no new data.
  230. line.string.setLen(0)
  231. while true:
  232. var c = readChar(s)
  233. if c == '\c':
  234. c = readChar(s)
  235. break
  236. elif c == '\L': break
  237. elif c == '\0':
  238. if line.len > 0: break
  239. else: return false
  240. line.string.add(c)
  241. result = true
  242. proc peekLine*(s: Stream, line: var TaintedString): bool =
  243. ## peeks a line of text from the stream `s` into `line`. `line` must not be
  244. ## ``nil``! May throw an IO exception.
  245. ## A line of text may be delimited by ``CR``, ``LF`` or
  246. ## ``CRLF``. The newline character(s) are not part of the returned string.
  247. ## Returns ``false`` if the end of the file has been reached, ``true``
  248. ## otherwise. If ``false`` is returned `line` contains no new data.
  249. let pos = getPosition(s)
  250. defer: setPosition(s, pos)
  251. result = readLine(s, line)
  252. proc readLine*(s: Stream): TaintedString =
  253. ## Reads a line from a stream `s`. Note: This is not very efficient. Raises
  254. ## `IOError` if an error occurred.
  255. result = TaintedString""
  256. if s.atEnd:
  257. raise newEIO("cannot read from stream")
  258. while true:
  259. var c = readChar(s)
  260. if c == '\c':
  261. c = readChar(s)
  262. break
  263. if c == '\L' or c == '\0':
  264. break
  265. else:
  266. result.string.add(c)
  267. proc peekLine*(s: Stream): TaintedString =
  268. ## Peeks a line from a stream `s`. Note: This is not very efficient. Raises
  269. ## `IOError` if an error occurred.
  270. let pos = getPosition(s)
  271. defer: setPosition(s, pos)
  272. result = readLine(s)
  273. iterator lines*(s: Stream): TaintedString =
  274. ## Iterates over every line in the stream.
  275. ## The iteration is based on ``readLine``.
  276. var line: TaintedString
  277. while s.readLine(line):
  278. yield line
  279. when not defined(js):
  280. type
  281. StringStream* = ref StringStreamObj ## a stream that encapsulates a string
  282. StringStreamObj* = object of StreamObj
  283. data*: string
  284. pos: int
  285. proc ssAtEnd(s: Stream): bool =
  286. var s = StringStream(s)
  287. return s.pos >= s.data.len
  288. proc ssSetPosition(s: Stream, pos: int) =
  289. var s = StringStream(s)
  290. s.pos = clamp(pos, 0, s.data.len)
  291. proc ssGetPosition(s: Stream): int =
  292. var s = StringStream(s)
  293. return s.pos
  294. proc ssReadData(s: Stream, buffer: pointer, bufLen: int): int =
  295. var s = StringStream(s)
  296. result = min(bufLen, s.data.len - s.pos)
  297. if result > 0:
  298. copyMem(buffer, addr(s.data[s.pos]), result)
  299. inc(s.pos, result)
  300. else:
  301. result = 0
  302. proc ssPeekData(s: Stream, buffer: pointer, bufLen: int): int =
  303. var s = StringStream(s)
  304. result = min(bufLen, s.data.len - s.pos)
  305. if result > 0:
  306. copyMem(buffer, addr(s.data[s.pos]), result)
  307. else:
  308. result = 0
  309. proc ssWriteData(s: Stream, buffer: pointer, bufLen: int) =
  310. var s = StringStream(s)
  311. if bufLen <= 0:
  312. return
  313. if s.pos + bufLen > s.data.len:
  314. setLen(s.data, s.pos + bufLen)
  315. copyMem(addr(s.data[s.pos]), buffer, bufLen)
  316. inc(s.pos, bufLen)
  317. proc ssClose(s: Stream) =
  318. var s = StringStream(s)
  319. when defined(nimNoNilSeqs):
  320. s.data = ""
  321. else:
  322. s.data = nil
  323. proc newStringStream*(s: string = ""): StringStream =
  324. ## creates a new stream from the string `s`.
  325. new(result)
  326. result.data = s
  327. result.pos = 0
  328. result.closeImpl = ssClose
  329. result.atEndImpl = ssAtEnd
  330. result.setPositionImpl = ssSetPosition
  331. result.getPositionImpl = ssGetPosition
  332. result.readDataImpl = ssReadData
  333. result.peekDataImpl = ssPeekData
  334. result.writeDataImpl = ssWriteData
  335. type
  336. FileStream* = ref FileStreamObj ## a stream that encapsulates a `File`
  337. FileStreamObj* = object of Stream
  338. f: File
  339. proc fsClose(s: Stream) =
  340. if FileStream(s).f != nil:
  341. close(FileStream(s).f)
  342. FileStream(s).f = nil
  343. proc fsFlush(s: Stream) = flushFile(FileStream(s).f)
  344. proc fsAtEnd(s: Stream): bool = return endOfFile(FileStream(s).f)
  345. proc fsSetPosition(s: Stream, pos: int) = setFilePos(FileStream(s).f, pos)
  346. proc fsGetPosition(s: Stream): int = return int(getFilePos(FileStream(s).f))
  347. proc fsReadData(s: Stream, buffer: pointer, bufLen: int): int =
  348. result = readBuffer(FileStream(s).f, buffer, bufLen)
  349. proc fsPeekData(s: Stream, buffer: pointer, bufLen: int): int =
  350. let pos = fsGetPosition(s)
  351. defer: fsSetPosition(s, pos)
  352. result = readBuffer(FileStream(s).f, buffer, bufLen)
  353. proc fsWriteData(s: Stream, buffer: pointer, bufLen: int) =
  354. if writeBuffer(FileStream(s).f, buffer, bufLen) != bufLen:
  355. raise newEIO("cannot write to stream")
  356. proc newFileStream*(f: File): FileStream =
  357. ## creates a new stream from the file `f`.
  358. new(result)
  359. result.f = f
  360. result.closeImpl = fsClose
  361. result.atEndImpl = fsAtEnd
  362. result.setPositionImpl = fsSetPosition
  363. result.getPositionImpl = fsGetPosition
  364. result.readDataImpl = fsReadData
  365. result.peekDataImpl = fsPeekData
  366. result.writeDataImpl = fsWriteData
  367. result.flushImpl = fsFlush
  368. proc newFileStream*(filename: string, mode: FileMode = fmRead, bufSize: int = -1): FileStream =
  369. ## creates a new stream from the file named `filename` with the mode `mode`.
  370. ## If the file cannot be opened, nil is returned. See the `system
  371. ## <system.html>`_ module for a list of available FileMode enums.
  372. ## **This function returns nil in case of failure. To prevent unexpected
  373. ## behavior and ensure proper error handling, use openFileStream instead.**
  374. var f: File
  375. if open(f, filename, mode, bufSize): result = newFileStream(f)
  376. proc openFileStream*(filename: string, mode: FileMode = fmRead, bufSize: int = -1): FileStream =
  377. ## creates a new stream from the file named `filename` with the mode `mode`.
  378. ## If the file cannot be opened, an IO exception is raised.
  379. var f: File
  380. if open(f, filename, mode, bufSize):
  381. return newFileStream(f)
  382. else:
  383. raise newEIO("cannot open file")
  384. when true:
  385. discard
  386. else:
  387. type
  388. FileHandleStream* = ref FileHandleStreamObj
  389. FileHandleStreamObj* = object of Stream
  390. handle*: FileHandle
  391. pos: int
  392. proc newEOS(msg: string): ref OSError =
  393. new(result)
  394. result.msg = msg
  395. proc hsGetPosition(s: FileHandleStream): int =
  396. return s.pos
  397. when defined(windows):
  398. # do not import windows as this increases compile times:
  399. discard
  400. else:
  401. import posix
  402. proc hsSetPosition(s: FileHandleStream, pos: int) =
  403. discard lseek(s.handle, pos, SEEK_SET)
  404. proc hsClose(s: FileHandleStream) = discard close(s.handle)
  405. proc hsAtEnd(s: FileHandleStream): bool =
  406. var pos = hsGetPosition(s)
  407. var theEnd = lseek(s.handle, 0, SEEK_END)
  408. result = pos >= theEnd
  409. hsSetPosition(s, pos) # set position back
  410. proc hsReadData(s: FileHandleStream, buffer: pointer, bufLen: int): int =
  411. result = posix.read(s.handle, buffer, bufLen)
  412. inc(s.pos, result)
  413. proc hsPeekData(s: FileHandleStream, buffer: pointer, bufLen: int): int =
  414. result = posix.read(s.handle, buffer, bufLen)
  415. proc hsWriteData(s: FileHandleStream, buffer: pointer, bufLen: int) =
  416. if posix.write(s.handle, buffer, bufLen) != bufLen:
  417. raise newEIO("cannot write to stream")
  418. inc(s.pos, bufLen)
  419. proc newFileHandleStream*(handle: FileHandle): FileHandleStream =
  420. new(result)
  421. result.handle = handle
  422. result.pos = 0
  423. result.close = hsClose
  424. result.atEnd = hsAtEnd
  425. result.setPosition = hsSetPosition
  426. result.getPosition = hsGetPosition
  427. result.readData = hsReadData
  428. result.peekData = hsPeekData
  429. result.writeData = hsWriteData
  430. proc newFileHandleStream*(filename: string,
  431. mode: FileMode): FileHandleStream =
  432. when defined(windows):
  433. discard
  434. else:
  435. var flags: cint
  436. case mode
  437. of fmRead: flags = posix.O_RDONLY
  438. of fmWrite: flags = O_WRONLY or int(O_CREAT)
  439. of fmReadWrite: flags = O_RDWR or int(O_CREAT)
  440. of fmReadWriteExisting: flags = O_RDWR
  441. of fmAppend: flags = O_WRONLY or int(O_CREAT) or O_APPEND
  442. var handle = open(filename, flags)
  443. if handle < 0: raise newEOS("posix.open() call failed")
  444. result = newFileHandleStream(handle)
  445. when isMainModule and defined(testing):
  446. var ss = newStringStream("The quick brown fox jumped over the lazy dog.\nThe lazy dog ran")
  447. assert(ss.getPosition == 0)
  448. assert(ss.peekStr(5) == "The q")
  449. assert(ss.getPosition == 0) # haven't moved
  450. assert(ss.readStr(5) == "The q")
  451. assert(ss.getPosition == 5) # did move
  452. assert(ss.peekLine() == "uick brown fox jumped over the lazy dog.")
  453. assert(ss.getPosition == 5) # haven't moved
  454. var str = newString(100)
  455. assert(ss.peekLine(str))
  456. assert(str == "uick brown fox jumped over the lazy dog.")
  457. assert(ss.getPosition == 5) # haven't moved