blob: 38435de254422deb914bdd3c2c023fa1095436b5 [file] [log] [blame]
(* MemStream.mod provide a memory stream channel.
Copyright (C) 2015-2023 Free Software Foundation, Inc.
Contributed by Gaius Mulley <gaius.mulley@southwales.ac.uk>.
This file is part of GNU Modula-2.
GNU Modula-2 is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3, or (at your option)
any later version.
GNU Modula-2 is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
Under Section 7 of GPL version 3, you are granted additional
permissions described in the GCC Runtime Library Exception, version
3.1, as published by the Free Software Foundation.
You should have received a copy of the GNU General Public License and
a copy of the GCC Runtime Library Exception along with this program;
see the files COPYING3 and COPYING.RUNTIME respectively. If not, see
<http://www.gnu.org/licenses/>. *)
IMPLEMENTATION MODULE MemStream ;
FROM RTgen IMPORT ChanDev, DeviceType,
InitChanDev, doLook, doSkip, doSkipLook, doWriteLn,
doReadText, doWriteText, doReadLocs, doWriteLocs,
checkErrno ;
FROM RTdata IMPORT ModuleId, MakeModuleId, InitData, GetData, KillData ;
FROM IOLink IMPORT DeviceId, DeviceTablePtr, IsDevice, MakeChan, UnMakeChan,
DeviceTablePtrValue, RAISEdevException, AllocateDeviceId,
ResetProc ;
FROM Builtins IMPORT memcpy ;
FROM Assertion IMPORT Assert ;
FROM Strings IMPORT Assign ;
FROM RTgenif IMPORT GenDevIF, InitGenDevIF ;
FROM FIO IMPORT File ;
FROM IOConsts IMPORT ReadResults ;
FROM ChanConsts IMPORT readFlag, writeFlag ;
FROM SYSTEM IMPORT ADDRESS, ADR ;
FROM ASCII IMPORT nl, nul ;
FROM Storage IMPORT ALLOCATE, DEALLOCATE, REALLOCATE ;
FROM libc IMPORT printf ;
IMPORT SYSTEM, RTio, errno, ErrnoCategory, ChanConsts, IOChan ;
CONST
InitialLength = 128 ;
Debugging = FALSE ;
TYPE
PtrToLoc = POINTER TO LOC ;
PtrToChar = POINTER TO CHAR ;
PtrToAddress = POINTER TO ADDRESS ;
PtrToCardinal = POINTER TO CARDINAL ;
MemInfo = POINTER TO RECORD
buffer: ADDRESS ;
length: CARDINAL ;
index : CARDINAL ;
pBuffer: PtrToAddress ;
pLength: PtrToCardinal ;
pUsed : PtrToCardinal ;
dealloc: BOOLEAN ;
eof : BOOLEAN ;
eoln : BOOLEAN ;
END ;
VAR
dev: ChanDev ;
did: DeviceId ;
mid: ModuleId ;
(*
Min -
*)
PROCEDURE Min (a, b: CARDINAL) : CARDINAL ;
BEGIN
IF a<b
THEN
RETURN( a )
ELSE
RETURN( b )
END
END Min ;
PROCEDURE look (d: DeviceTablePtr;
VAR ch: CHAR; VAR r: ReadResults) ;
BEGIN
doLook(dev, d, ch, r)
END look ;
PROCEDURE skip (d: DeviceTablePtr) ;
BEGIN
doSkip(dev, d)
END skip ;
PROCEDURE skiplook (d: DeviceTablePtr;
VAR ch: CHAR; VAR r: ReadResults) ;
BEGIN
doSkipLook(dev, d, ch, r)
END skiplook ;
PROCEDURE lnwrite (d: DeviceTablePtr) ;
BEGIN
doWriteLn(dev, d)
END lnwrite ;
PROCEDURE textread (d: DeviceTablePtr;
to: SYSTEM.ADDRESS;
maxChars: CARDINAL;
VAR charsRead: CARDINAL) ;
BEGIN
doReadText(dev, d, to, maxChars, charsRead)
END textread ;
PROCEDURE textwrite (d: DeviceTablePtr;
from: SYSTEM.ADDRESS;
charsToWrite: CARDINAL);
BEGIN
doWriteText(dev, d, from, charsToWrite)
END textwrite ;
PROCEDURE rawread (d: DeviceTablePtr;
to: SYSTEM.ADDRESS;
maxLocs: CARDINAL;
VAR locsRead: CARDINAL) ;
BEGIN
doReadLocs(dev, d, to, maxLocs, locsRead)
END rawread ;
PROCEDURE rawwrite (d: DeviceTablePtr;
from: SYSTEM.ADDRESS;
locsToWrite: CARDINAL) ;
BEGIN
doWriteLocs(dev, d, from, locsToWrite)
END rawwrite ;
PROCEDURE getname (d: DeviceTablePtr;
VAR a: ARRAY OF CHAR) ;
BEGIN
Assign('memstream', a)
END getname ;
PROCEDURE flush (d: DeviceTablePtr) ;
BEGIN
(* nothing to do *)
END flush ;
(*
doreadchar - returns a CHAR from the file associated with, g.
*)
PROCEDURE doreadchar (g: GenDevIF; d: DeviceTablePtr) : CHAR ;
VAR
m : MemInfo ;
pc: PtrToChar ;
BEGIN
WITH d^ DO
m := GetData(d, mid) ;
WITH m^ DO
IF index<length
THEN
pc := buffer ;
INC(pc, index) ;
INC(index) ;
AssignIndex(m, index) ;
eoln := (pc^=nl) ;
eof := FALSE ;
RETURN( pc^ )
ELSE
eof := TRUE ;
eoln := FALSE ;
RETURN( nul )
END
END
END
END doreadchar ;
(*
dounreadchar - pushes a CHAR back onto the file associated with, g.
*)
PROCEDURE dounreadchar (g: GenDevIF; d: DeviceTablePtr; ch: CHAR) : CHAR ;
VAR
m : MemInfo ;
pc: PtrToChar ;
BEGIN
WITH d^ DO
m := GetData(d, mid) ;
WITH m^ DO
IF index>0
THEN
DEC(index) ;
AssignIndex(m, index) ;
eof := FALSE ;
pc := buffer ;
INC(pc, index) ;
eoln := (ch=nl) ;
Assert(pc^=ch) (* expecting to be pushing characters in exactly the reverse order *)
ELSE
Assert(FALSE) ; (* expecting to be pushing characters in exactly the reverse order *)
END
END ;
RETURN( ch )
END
END dounreadchar ;
(*
dogeterrno - always return 0 as the memstream device never invokes errno.
*)
PROCEDURE dogeterrno (g: GenDevIF; d: DeviceTablePtr) : INTEGER ;
BEGIN
RETURN 0
END dogeterrno ;
(*
dorbytes - reads upto, max, bytes setting, actual, and
returning FALSE if an error (not due to eof)
occurred.
*)
PROCEDURE dorbytes (g: GenDevIF; d: DeviceTablePtr;
to: ADDRESS;
max: CARDINAL;
VAR actual: CARDINAL) : BOOLEAN ;
VAR
m : MemInfo ;
pl: PtrToLoc ;
BEGIN
WITH d^ DO
m := GetData(d, mid) ;
WITH m^ DO
pl := buffer ;
INC(pl, index) ;
actual := Min(max, length-index) ;
to := memcpy(to, pl, actual) ;
INC(index, actual) ;
AssignIndex(m, index) ;
eof := FALSE ;
eoln := FALSE
END ;
RETURN( TRUE )
END
END dorbytes ;
(*
dowbytes -
*)
PROCEDURE dowbytes (g: GenDevIF; d: DeviceTablePtr;
from: ADDRESS;
nBytes: CARDINAL;
VAR actual: CARDINAL) : BOOLEAN ;
VAR
m : MemInfo ;
pl: PtrToLoc ;
BEGIN
WITH d^ DO
m := GetData(d, mid) ;
WITH m^ DO
IF index+nBytes>length
THEN
WHILE index+nBytes>length DO
(* buffer needs to grow *)
length := length*2
END ;
REALLOCATE(buffer, length) ;
AssignLength(m, length) ;
AssignBuffer(m, buffer)
END ;
pl := buffer ;
INC(pl, index) ;
actual := Min(nBytes, length-index) ;
pl := memcpy(pl, from, actual) ;
INC(index, actual) ;
AssignIndex(m, index)
END ;
RETURN( TRUE )
END
END dowbytes ;
(*
dowriteln - attempt to write an end of line marker to the
file and returns TRUE if successful.
*)
PROCEDURE dowriteln (g: GenDevIF; d: DeviceTablePtr) : BOOLEAN ;
VAR
ch: CHAR ;
n : CARDINAL ;
BEGIN
ch := nl ;
RETURN( dowbytes(g, d, ADR(ch), SIZE(ch), n) )
END dowriteln ;
(*
iseof - returns TRUE if end of file has been seen.
*)
PROCEDURE iseof (g: GenDevIF; d: DeviceTablePtr) : BOOLEAN ;
VAR
m: MemInfo ;
BEGIN
IF Debugging
THEN
printf ("mid = %p, d = %p\n", mid, d)
END ;
WITH d^ DO
IF Debugging
THEN
printf ("mid = %p, d = %p\n", mid, d)
END ;
m := GetData(d, mid) ;
RETURN( m^.eof )
END
END iseof ;
(*
iseoln - returns TRUE if end of line is seen.
*)
PROCEDURE iseoln (g: GenDevIF; d: DeviceTablePtr) : BOOLEAN ;
VAR
m: MemInfo ;
BEGIN
WITH d^ DO
m := GetData(d, mid) ;
RETURN( m^.eoln )
END
END iseoln ;
(*
iserror - returns TRUE if an error was seen on the device.
*)
PROCEDURE iserror (g: GenDevIF; d: DeviceTablePtr) : BOOLEAN ;
BEGIN
RETURN( FALSE )
END iserror ;
(*
AssignLength -
*)
PROCEDURE AssignLength (m: MemInfo; l: CARDINAL) ;
BEGIN
WITH m^ DO
length := l ;
IF pLength#NIL
THEN
pLength^ := l
END
END
END AssignLength ;
(*
AssignBuffer -
*)
PROCEDURE AssignBuffer (m: MemInfo; b: ADDRESS) ;
BEGIN
WITH m^ DO
buffer := b ;
IF pBuffer#NIL
THEN
pBuffer^ := b
END
END
END AssignBuffer ;
(*
AssignIndex -
*)
PROCEDURE AssignIndex (m: MemInfo; i: CARDINAL) ;
BEGIN
WITH m^ DO
index := i ;
IF pUsed#NIL
THEN
pUsed^ := i
END
END
END AssignIndex ;
(*
newCidWrite - returns a ChanId which represents the opened file, name.
res is set appropriately on return.
*)
PROCEDURE newCidWrite (f: FlagSet;
VAR res: OpenResults;
VAR buffer: ADDRESS;
VAR length: CARDINAL;
VAR used: CARDINAL;
deallocOnClose: BOOLEAN) : ChanId ;
VAR
c: ChanId ;
d: DeviceTablePtr ;
m: MemInfo ;
BEGIN
MakeChan(did, c) ;
d := DeviceTablePtrValue(c, did) ;
NEW(m) ;
m^.pBuffer := ADR(buffer) ;
m^.pLength := ADR(length) ;
m^.pUsed := ADR(used) ;
m^.dealloc := deallocOnClose ;
ALLOCATE(m^.buffer, InitialLength) ;
AssignBuffer(m, m^.buffer) ;
AssignLength(m, InitialLength) ;
AssignIndex(m, 0) ;
InitData(d, mid, m, freeMemInfo) ;
WITH d^ DO
flags := f ;
errNum := 0 ;
doLook := look ;
doSkip := skip ;
doSkipLook := skiplook ;
doLnWrite := lnwrite ;
doTextRead := textread ;
doTextWrite := textwrite ;
doRawRead := rawread ;
doRawWrite := rawwrite ;
doGetName := getname ;
doReset := resetWrite ;
doFlush := flush ;
doFree := handlefree
END ;
res := opened ;
RETURN( c )
END newCidWrite ;
(*
Attempts to obtain and open a channel connected to a contigeous
buffer in memory. The write flag is implied; without the raw
flag, text is implied. If successful, assigns to cid the identity of
the opened channel, assigns the value opened to res.
If a channel cannot be opened as required,
the value of res indicates the reason, and cid identifies the
invalid channel.
The parameters, buffer, length and used maybe updated as
data is written. The buffer maybe reallocated
and its address might alter, however the parameters will
always reflect the current active buffer. When this
channel is closed the buffer is deallocated and
buffer will be set to NIL, length and used will be set to
zero.
*)
PROCEDURE OpenWrite (VAR cid: ChanId; flags: FlagSet;
VAR res: OpenResults;
VAR buffer: ADDRESS;
VAR length: CARDINAL;
VAR used: CARDINAL;
deallocOnClose: BOOLEAN) ;
BEGIN
IF Debugging
THEN
printf ("OpenWrite called\n")
END ;
INCL(flags, ChanConsts.writeFlag) ;
IF NOT (ChanConsts.rawFlag IN flags)
THEN
INCL(flags, ChanConsts.textFlag)
END ;
cid := newCidWrite(flags, res, buffer, length, used, deallocOnClose)
END OpenWrite ;
(*
newCidRead - returns a ChanId which represents the opened file, name.
res is set appropriately on return.
*)
PROCEDURE newCidRead (f: FlagSet;
VAR res: OpenResults;
buffer: ADDRESS;
length: CARDINAL;
deallocOnClose: BOOLEAN) : ChanId ;
VAR
c: ChanId ;
d: DeviceTablePtr ;
m: MemInfo ;
BEGIN
MakeChan(did, c) ;
d := DeviceTablePtrValue(c, did) ;
NEW(m) ;
m^.pBuffer := NIL ;
m^.pLength := NIL ;
m^.pUsed := NIL ;
m^.dealloc := deallocOnClose ;
AssignBuffer(m, buffer) ;
AssignLength(m, length) ;
AssignIndex(m, 0) ;
InitData(d, mid, m, freeMemInfo) ;
WITH d^ DO
flags := f ;
errNum := 0 ;
doLook := look ;
doSkip := skip ;
doSkipLook := skiplook ;
doLnWrite := lnwrite ;
doTextRead := textread ;
doTextWrite := textwrite ;
doRawRead := rawread ;
doRawWrite := rawwrite ;
doGetName := getname ;
doReset := resetRead ;
doFlush := flush ;
doFree := handlefree
END ;
res := opened ;
RETURN( c )
END newCidRead ;
(*
freeMemInfo -
*)
PROCEDURE freeMemInfo (a: ADDRESS) ;
VAR
m: MemInfo ;
BEGIN
DEALLOCATE(a, SIZE(m^))
END freeMemInfo ;
(*
Attempts to obtain and open a channel connected to a contigeous
buffer in memory. The read and old flags are implied; without
the raw flag, text is implied. If successful, assigns to cid the
identity of the opened channel, assigns the value opened to res, and
selects input mode, with the read position corresponding to the start
of the buffer. If a channel cannot be opened as required, the value of
res indicates the reason, and cid identifies the invalid channel.
*)
PROCEDURE OpenRead (VAR cid: ChanId; flags: FlagSet;
VAR res: OpenResults;
buffer: ADDRESS; length: CARDINAL;
deallocOnClose: BOOLEAN) ;
BEGIN
flags := flags + ChanConsts.read + ChanConsts.old ;
IF NOT (ChanConsts.rawFlag IN flags)
THEN
INCL(flags, ChanConsts.textFlag)
END ;
cid := newCidRead(flags, res, buffer, length, deallocOnClose)
END OpenRead ;
(*
resetRead - wrap a call to Reread.
*)
PROCEDURE resetRead (d: DeviceTablePtr) ;
BEGIN
Reread(d^.cid)
END resetRead ;
(*
resetWrite - wrap a call to Rewrite.
*)
PROCEDURE resetWrite (d: DeviceTablePtr) ;
BEGIN
Rewrite(d^.cid)
END resetWrite ;
(*
Reread - if the channel identified by cid is not open
to a memory stream, the exception
wrongDevice is raised; otherwise it sets the
index to 0. Subsequent reads will read the
previous buffer contents.
*)
PROCEDURE Reread (cid: ChanId) ;
VAR
d: DeviceTablePtr ;
m: MemInfo ;
BEGIN
IF IsMem(cid)
THEN
d := DeviceTablePtrValue(cid, did) ;
WITH d^ DO
EXCL(flags, writeFlag) ;
IF readFlag IN flags
THEN
m := GetData(d, mid) ;
AssignIndex(m, 0)
ELSE
EXCL(flags, readFlag)
END
END
ELSE
RAISEdevException(cid, did, IOChan.wrongDevice,
'MemStream.' + __FUNCTION__ +
': channel is not a memory stream')
END
END Reread ;
(*
Rewrite - if the channel identified by cid is not open to a
memory stream, the exception wrongDevice
is raised; otherwise, it sets the index to 0.
Subsequent writes will overwrite the previous buffer
contents.
*)
PROCEDURE Rewrite (cid: ChanId) ;
VAR
d: DeviceTablePtr ;
m: MemInfo ;
BEGIN
IF IsMem(cid)
THEN
d := DeviceTablePtrValue(cid, did) ;
WITH d^ DO
EXCL(flags, readFlag) ;
IF writeFlag IN flags
THEN
m := GetData(d, mid) ;
AssignIndex(m, 0)
ELSE
EXCL(flags, writeFlag)
END
END
ELSE
RAISEdevException(cid, did, IOChan.wrongDevice,
'MemStream.' + __FUNCTION__ +
': channel is not a memory stream')
END
END Rewrite ;
(*
handlefree -
*)
PROCEDURE handlefree (d: DeviceTablePtr) ;
BEGIN
END handlefree ;
(*
Close - if the channel identified by cid is not open to a sequential
stream, the exception wrongDevice is raised; otherwise
closes the channel, and assigns the value identifying
the invalid channel to cid.
*)
PROCEDURE Close (VAR cid: ChanId) ;
BEGIN
printf ("Close called\n");
IF IsMem(cid)
THEN
UnMakeChan(did, cid) ;
cid := IOChan.InvalidChan()
ELSE
RAISEdevException(cid, did, IOChan.wrongDevice,
'MemStream.' + __FUNCTION__ +
': channel is not a sequential file')
END
END Close ;
(*
IsMem - tests if the channel identified by cid is open as
a memory stream.
*)
PROCEDURE IsMem (cid: ChanId) : BOOLEAN ;
BEGIN
RETURN( (cid # NIL) AND (IOChan.InvalidChan() # cid) AND
(IsDevice(cid, did)) AND
((ChanConsts.readFlag IN IOChan.CurrentFlags(cid)) OR
(ChanConsts.writeFlag IN IOChan.CurrentFlags(cid))) )
END IsMem ;
(*
Init -
*)
PROCEDURE Init ;
VAR
gen: GenDevIF ;
BEGIN
MakeModuleId(mid) ;
IF Debugging
THEN
printf ("mid = %d\n", mid)
END ;
AllocateDeviceId(did) ;
gen := InitGenDevIF(did, doreadchar, dounreadchar,
dogeterrno, dorbytes, dowbytes,
dowriteln,
iseof, iseoln, iserror) ;
dev := InitChanDev(streamfile, did, gen)
END Init ;
BEGIN
Init
END MemStream.