-- StreamsB.Mesa Edited by Sandman on June 10, 1980 11:33 AM -- Copyright Xerox Corporation 1979, 1980 DIRECTORY AltoDefs USING [CharsPerPage, MaxFilePage, PageCount, PageNumber], AltoFileDefs USING [CFA, eofDA, FA, fillinDA, vDA], BFSDefs USING [DeletePages], ForgotDefs USING [StreamBufferDS], InlineDefs USING [BITAND, LongDivMod, LongMult], NucleusOps USING [], SegmentDefs USING [ Append, DataSegmentHandle, DataSegmentAddress, DefaultAccess, DefaultBase, FileHandle, GetFileLength, InsertFileLength, JumpToPage, LockFile, HardDown, MakeDataSegment, NewFile, OpenFile, Read, ReleaseFile, SetFileAccess, UnlockFile, UpdateFileLength, Write], StreamDefs USING [ AccessOptions, DiskHandle, StreamHandle, StreamIndex, StreamObject, StreamPosition], StreamsA USING [ Cleanup, EndOf, Fixup, Pos, PositionByte, ReadByte, ReadWord, StreamError, TransferPages, WriteByte, WriteWord], Storage USING [Node, Free, FreePages]; StreamsB: PROGRAM IMPORTS BFSDefs, InlineDefs, SegmentDefs, Storage, StreamsA EXPORTS StreamDefs, NucleusOps SHARES StreamsA, StreamDefs, SegmentDefs =PUBLIC BEGIN OPEN AltoDefs, AltoFileDefs, StreamDefs, StreamsA; WindowSize: PageCount = 1; NewByteStream: PROCEDURE [name: STRING, access: AccessOptions] RETURNS [DiskHandle] = BEGIN OPEN SegmentDefs; RETURN[Create[NewFile[name, access], bytes, access]] END; NewWordStream: PROCEDURE [name: STRING, access: AccessOptions] RETURNS [DiskHandle] = BEGIN OPEN SegmentDefs; RETURN[Create[NewFile[name, access], words, access]] END; CreateByteStream: PROCEDURE [ file: SegmentDefs.FileHandle, access: AccessOptions] RETURNS [DiskHandle] = BEGIN RETURN[Create[file, bytes, access]] END; CreateWordStream: PROCEDURE [ file: SegmentDefs.FileHandle, access: AccessOptions] RETURNS [DiskHandle] = BEGIN RETURN[Create[file, words, access]] END; streamList: DiskHandle _ NIL; GetDiskStreamList: PROCEDURE RETURNS [DiskHandle] = BEGIN RETURN[streamList] END; Create: PROCEDURE [ file: SegmentDefs.FileHandle, units: {bytes, words}, access: AccessOptions] RETURNS [stream: DiskHandle] = BEGIN OPEN SegmentDefs; fa: FA _ FA[eofDA, 0, 0]; IF access = DefaultAccess THEN access _ Read; SetFileAccess[file, access]; stream _ Storage.Node[SIZE[Disk StreamObject]]; stream^ _ StreamObject[ reset: Reset, get: ReadByte, putback: PutBack, put: WriteByte, endof: EndOf, destroy: Destroy, link: streamList, body: Disk[ eof: FALSE, dirty: FALSE, unit: 1, index: 0, size: 0, getOverflow: Fixup, savedGet: ReadError, putOverflow: Fixup, savedPut: WriteByte, file:, read:, write:, append:, page: 0, char: 0, buffer:, das:]]; stream.file _ file; stream.read _ InlineDefs.BITAND[access, Read] # 0; stream.write _ InlineDefs.BITAND[access, Write] # 0; stream.append _ InlineDefs.BITAND[access, Append] # 0; IF units = words THEN BEGIN OPEN stream; get _ ReadWord; unit _ 2; put _ savedPut _ WriteWord; END; IF ~stream.read THEN stream.get _ ReadError; SELECT InlineDefs.BITAND[access, Write + Append] FROM 0 => stream.put _ stream.savedPut _ WriteError; Write => stream.savedPut _ WriteError; Append => stream.put _ WriteError; ENDCASE; stream.buffer.word _ GetBuffer[]; BEGIN ENABLE UNWIND => Storage.FreePages[stream.buffer.word]; LockFile[file]; InsertFileLength[file, @fa]; OpenFile[file]; stream.das[last] _ stream.das[next] _ fillinDA; stream.das[current] _ file.fp.leaderDA; IF access = Append THEN [] _ FileLength[stream] ELSE Jump[stream, @fa, 1]; END; streamList _ stream; RETURN END; GetBuffer: PROCEDURE RETURNS [POINTER] = BEGIN OPEN SegmentDefs; ds: DataSegmentHandle _ MakeDataSegment[ base: DefaultBase, pages: WindowSize, info: HardDown]; ds.type _ ForgotDefs.StreamBufferDS; RETURN[DataSegmentAddress[ds]]; END; OpenDiskStream: PROCEDURE [stream: StreamHandle] = BEGIN fa: FA; WITH s: stream SELECT FROM Disk => BEGIN IF s.buffer.word = NIL THEN s.buffer.word _ GetBuffer[]; fa _ FA[s.das[current], s.page, Pos[@s]]; JumpToFA[@s, @fa]; END; ENDCASE => SIGNAL StreamError[@s, StreamType]; RETURN END; CleanupDiskStream: PROCEDURE [stream: StreamHandle] = BEGIN WITH s: stream SELECT FROM Disk => Cleanup[@s, TRUE]; ENDCASE => SIGNAL StreamError[@s, StreamType]; RETURN END; Reset: PROCEDURE [stream: StreamHandle] = BEGIN fa: FA; WITH s: stream SELECT FROM Disk => BEGIN fa _ FA[eofDA, 0, 0]; Jump[@s, @fa, 1]; END; ENDCASE => SIGNAL StreamError[@s, StreamType]; RETURN END; CloseDiskStream: PROCEDURE [stream: StreamHandle] = BEGIN WITH s: stream SELECT FROM Disk => BEGIN Cleanup[@s, TRUE]; Storage.FreePages[s.buffer.word]; s.buffer.word _ NIL; END; ENDCASE => SIGNAL StreamError[@s, StreamType]; RETURN END; TruncateDiskStream: PROCEDURE [stream: StreamHandle] = BEGIN WITH s: stream SELECT FROM Disk => Kill[@s, s.write]; ENDCASE => SIGNAL StreamError[@s, StreamType]; RETURN END; Destroy: PROCEDURE [stream: StreamHandle] = BEGIN WITH s: stream SELECT FROM Disk => Kill[@s, ~s.read AND GetIndex[@s] # StreamIndex[0, 0]]; ENDCASE => SIGNAL StreamError[@s, StreamType]; RETURN END; Kill: PROCEDURE [stream: DiskHandle, trunc: BOOLEAN] = BEGIN OPEN stream; da: vDA; pn: PageNumber; prev: StreamHandle; IF streamList = stream THEN streamList _ LOOPHOLE[stream.link] ELSE FOR prev _ streamList, prev.link UNTIL prev = NIL DO IF prev.link = stream THEN prev.link _ stream.link; ENDLOOP; BEGIN ENABLE UNWIND => BEGIN IF buffer.word # NIL THEN Storage.FreePages[buffer.word]; Storage.Free[stream]; END; IF buffer.word # NIL THEN BEGIN da _ eofDA; IF trunc THEN BEGIN -- truncate the file -- this is not a separate procedure because it -- leaves the stream buffer in an awful state. pn _ page; da _ das[next]; das[next] _ eofDA; IF char # Pos[stream] THEN BEGIN char _ Pos[stream]; dirty _ TRUE END; END; IF dirty THEN Cleanup[stream, TRUE]; IF da # eofDA THEN BFSDefs.DeletePages[buffer.word, @file.fp, da, pn + 1]; Storage.FreePages[buffer.word]; buffer.word _ NIL; END; SegmentDefs.UnlockFile[file]; IF file.segcount = 0 THEN SegmentDefs.ReleaseFile[file]; END; --ELBANE Storage.Free[stream]; RETURN END; Jump: PROCEDURE [s: DiskHandle, fa: POINTER TO FA, pn: PageNumber] = BEGIN OPEN SegmentDefs, s; cfa: CFA _ CFA[file.fp, fa^]; foundEnd: BOOLEAN _ FALSE; IF dirty THEN Cleanup[s, TRUE]; PositionByte[s, 0, FALSE]; IF pn = MaxFilePage THEN BEGIN [das[last], das[next]] _ JumpToPage[@cfa, cfa.fa.page, buffer.word]; foundEnd _ das[next] = AltoFileDefs.eofDA; END; IF ~foundEnd THEN [das[last], das[next]] _ JumpToPage[@cfa, pn, buffer.word]; [das[current], page, char] _ cfa.fa; IF das[next] = eofDA THEN SegmentDefs.UpdateFileLength[file, @cfa.fa]; PositionByte[s, IF page # pn THEN char ELSE MIN[char, fa.byte], FALSE]; RETURN END; ReadError: PROCEDURE [s: StreamHandle] RETURNS [UNSPECIFIED] = BEGIN SIGNAL StreamError[s, StreamAccess]; RETURN[0] END; PutBack: PROCEDURE [stream: StreamHandle, item: UNSPECIFIED] = BEGIN SIGNAL StreamError[stream, StreamOperation]; RETURN END; WriteError: PROCEDURE [stream: StreamHandle, item: UNSPECIFIED] = BEGIN SIGNAL StreamError[stream, StreamAccess]; RETURN END; bite: INTEGER = 60; -- don't use too much heap PositionPage: PROCEDURE [s: DiskHandle, p: PageNumber] = BEGIN d, dp: INTEGER; np: PageCount; Cleanup[s, TRUE]; PositionByte[s, 0, FALSE]; -- should we reset first? SELECT INTEGER[s.page - p] FROM <= 0 => NULL; = 1, < INTEGER[s.page/10] => NULL; ENDCASE => Reset[s]; WHILE (d _ p - s.page) # 0 DO dp _ IF d < 0 THEN -1 ELSE MIN[d, bite]; np _ TransferPages[s, NIL, dp, in, FALSE]; IF dp > 0 AND np # dp THEN EXIT; REPEAT FINISHED => RETURN; ENDLOOP; IF ~s.append THEN ERROR StreamError[s, StreamAccess]; -- extend the file (the first transfer flushes the buffer) IF s.char > 0 THEN [] _ TransferPages[s, NIL, 1, out, FALSE]; WHILE (d _ p - s.page) # 0 DO [] _ TransferPages[s, NIL, MIN[d, bite], out, FALSE]; ENDLOOP; RETURN END; -- StreamPosition Manipulation GetPosition: PROCEDURE [stream: StreamHandle] RETURNS [StreamPosition] = BEGIN WITH s: stream SELECT FROM Disk => BEGIN -- make sure we're not at end of page Cleanup[@s, FALSE]; -- don't flush RETURN[Pos[@s] + InlineDefs.LongMult[s.page - 1, CharsPerPage]]; END; ENDCASE => SIGNAL StreamError[@s, StreamType]; RETURN[0] END; SetPosition: PROCEDURE [stream: StreamHandle, pos: StreamPosition] = BEGIN page, byte: CARDINAL; WITH s: stream SELECT FROM Disk => BEGIN [quotient: page, remainder: byte] _ InlineDefs.LongDivMod[ pos, CharsPerPage]; IF page + 1 # s.page THEN PositionPage[@s, page + 1]; PositionByte[@s, byte, FALSE]; END; ENDCASE => SIGNAL StreamError[@s, StreamType]; RETURN END; -- StreamIndex Manipulation GetIndex: PROCEDURE [stream: StreamHandle] RETURNS [StreamIndex] = BEGIN WITH s: stream SELECT FROM Disk => BEGIN -- make sure we're not at end of page Cleanup[@s, FALSE]; -- don't flush RETURN[StreamIndex[s.page - 1, Pos[@s]]]; END; ENDCASE => SIGNAL StreamError[@s, StreamType]; RETURN[StreamIndex[0, 0]] END; SetIndex: PROCEDURE [stream: StreamHandle, index: StreamIndex] = BEGIN WITH s: stream SELECT FROM Disk => BEGIN index _ NormalizeIndex[index]; IF index.page + 1 # s.page THEN PositionPage[@s, index.page + 1]; PositionByte[@s, index.byte, FALSE]; END; ENDCASE => SIGNAL StreamError[@s, StreamType]; RETURN END; NormalizeIndex: PROCEDURE [index: StreamIndex] RETURNS [StreamIndex] = BEGIN IF index.byte >= CharsPerPage THEN BEGIN index.page _ index.page + index.byte/CharsPerPage; index.byte _ index.byte MOD CharsPerPage; END; RETURN[index] END; ModifyIndex: PROCEDURE [index: StreamIndex, change: INTEGER] RETURNS [StreamIndex] = BEGIN OPEN AltoDefs; delta: CARDINAL = ABS[change]; pages: CARDINAL _ delta/CharsPerPage; bytes: CARDINAL _ delta MOD CharsPerPage; index _ NormalizeIndex[index]; SELECT change FROM > 0 => BEGIN bytes _ index.byte + bytes; IF bytes >= CharsPerPage THEN BEGIN bytes _ bytes - CharsPerPage; pages _ pages + 1 END; pages _ index.page + pages; END; = 0 => RETURN[index]; < 0 => BEGIN IF bytes <= index.byte THEN bytes _ index.byte - bytes ELSE BEGIN bytes _ index.byte + CharsPerPage - bytes; pages _ pages + 1 END; IF pages <= index.page THEN pages _ index.page - pages ELSE RETURN[[0, 0]]; END; ENDCASE; RETURN[[pages, bytes]]; END; -- procedures to test for equality of stream indexes EqualIndex: PROCEDURE [i1, i2: StreamIndex] RETURNS [BOOLEAN] = BEGIN i1 _ NormalizeIndex[i1]; i2 _ NormalizeIndex[i2]; RETURN[i1 = i2]; END; GrEqualIndex: PROCEDURE [i1, i2: StreamIndex] RETURNS [BOOLEAN] = BEGIN RETURN[EqualIndex[i1, i2] OR GrIndex[i1, i2]]; END; GrIndex: PROCEDURE [i1, i2: StreamIndex] RETURNS [BOOLEAN] = BEGIN i1 _ NormalizeIndex[i1]; i2 _ NormalizeIndex[i2]; RETURN[i1.page > i2.page OR (i1.page = i2.page AND i1.byte > i2.byte)]; END; GetFA: PROCEDURE [stream: StreamHandle, fa: POINTER TO FA] = BEGIN WITH s: stream SELECT FROM Disk => BEGIN -- make sure not at end of a page Cleanup[@s, FALSE]; -- don't flush fa^ _ FA[s.das[current], s.page, Pos[@s]]; END; ENDCASE => SIGNAL StreamError[@s, StreamType]; RETURN END; FileLength: PROCEDURE [stream: StreamHandle] RETURNS [StreamIndex] = BEGIN fa: FA; WITH s: stream SELECT FROM Disk => BEGIN SegmentDefs.GetFileLength[s.file, @fa]; Jump[@s, @fa, MaxFilePage]; SegmentDefs.UpdateFileLength[s.file, @fa]; RETURN[GetIndex[@s]]; END; ENDCASE => SIGNAL StreamError[@s, StreamType]; RETURN[StreamIndex[0, 0]] END; JumpToFA: PROCEDURE [stream: StreamHandle, fa: POINTER TO FA] = BEGIN WITH s: stream SELECT FROM Disk => BEGIN Jump[@s, fa, fa.page]; IF fa.page # s.page OR fa.byte # Pos[@s] THEN SIGNAL StreamError[@s, StreamEnd]; END; ENDCASE => SIGNAL StreamError[@s, StreamType]; RETURN END; END..