(* MemStream.mod provide a memory stream channel. Copyright (C) 2015-2023 Free Software Foundation, Inc. Contributed by Gaius Mulley . 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 . *) 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 a0 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.