Files
Nim/lib/pure/streams.nim

253 lines
8.0 KiB
Nim
Executable File

#
#
# 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)