-- 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..