-- StreamsA.Mesa Edited by Sandman on July 1, 1980 11:14 AM
-- Copyright Xerox Corporation 1979, 1980
DIRECTORY
AltoDefs USING [CharsPerPage, CharsPerWord, PageCount, PageNumber, PageSize],
AltoFileDefs USING [eofDA, FA, fillinDA, FP, vDA],
BFSDefs USING [ActOnPages, GetNextDA, WritePages],
DiskDefs USING [DiskRequest],
InlineDefs USING [BITAND, BITSHIFT, COPY],
MiscDefs USING [Zero],
NucleusOps USING [],
SegmentDefs USING [UpdateFileLength],
StreamDefs USING [DiskHandle, StreamErrorCode, StreamHandle],
Storage USING [Node, Free];
StreamsA: PROGRAM
IMPORTS BFSDefs, InlineDefs, MiscDefs, SegmentDefs, Storage
EXPORTS StreamDefs, NucleusOps
SHARES StreamDefs, SegmentDefs =
BEGIN OPEN AltoDefs, AltoFileDefs;
DiskHandle: TYPE = StreamDefs.DiskHandle;
StreamHandle: TYPE = StreamDefs.StreamHandle;
StreamErrorCode: TYPE = StreamDefs.StreamErrorCode;
StreamError: PUBLIC SIGNAL [stream: StreamHandle, error: StreamErrorCode] =
CODE;
-- block mode transfers
direction: TYPE = {in, out};
-- the fast stream overflow handler; should only be called
-- from the fast stream get, put, and endof routines. It
-- always supplies a new count (which may be zero, in which
-- case get and/or put is replaced with an error routine).
-- Cleanup makes the disk look like the stream, unless the
-- current page is not full and you didn't ask for a flush.
Fixup: PROCEDURE [stream: StreamHandle] =
BEGIN
pos: CARDINAL;
WITH s: stream SELECT FROM
Disk =>
BEGIN
Cleanup[@s, FALSE]; -- don't flush
IF (pos ← Pos[@s]) >= s.char THEN
BEGIN
SetEnd[@s, TRUE]; -- ran into eof
Setup[@s, pos, CharsPerPage];
END;
END;
ENDCASE => SIGNAL StreamError[@s, StreamType];
RETURN
END;
Cleanup: PROCEDURE [s: DiskHandle, flush: BOOLEAN] =
BEGIN
pos: CARDINAL;
IF (pos ← Pos[s]) > s.char THEN PositionByte[s, pos, FALSE];
IF pos = CharsPerPage OR
(s.char = CharsPerPage AND s.dirty AND s.das[next] = eofDA) THEN
BEGIN -- write current page, read (maybe create) next one
IF s.dirty THEN [] ← TransferPages[s, NIL, 1, out, FALSE]
-- donothing with current page, read next one
ELSE [] ← TransferPages[s, NIL, 1, in, TRUE];
IF pos # CharsPerPage THEN
BEGIN
[] ← TransferPages[s, NIL, -1, in, FALSE];
PositionByte[s, pos, FALSE];
END;
END
ELSE
IF s.dirty AND flush THEN
BEGIN -- write current page w/ new numChars
[] ← TransferPages[s, NIL, 0, out, TRUE];
PositionByte[s, pos, FALSE];
END;
RETURN
END;
ReadBlock: PUBLIC PROCEDURE [
stream: StreamHandle, address: POINTER, words: CARDINAL] RETURNS [CARDINAL] =
BEGIN
done: CARDINAL ← 0;
WITH s: stream SELECT FROM
Disk => IF s.read THEN done ← TransferBlock[@s, address, words, in];
ENDCASE => SIGNAL StreamError[@s, StreamType];
RETURN[done]
END;
WriteBlock: PUBLIC PROCEDURE [
stream: StreamHandle, address: POINTER, words: CARDINAL] RETURNS [CARDINAL] =
BEGIN
done: CARDINAL ← 0;
WITH s: stream SELECT FROM
Disk =>
IF (~s.write AND ~s.append) OR (~s.write AND s.append AND ~EndOf[@s]) OR
(s.write AND ~s.append AND EndOf[@s]) THEN NULL
ELSE done ← TransferBlock[@s, address, words, out];
ENDCASE => SIGNAL StreamError[@s, StreamType];
RETURN[done]
END;
TransferBlock: PROCEDURE [s: DiskHandle, a: POINTER, n: CARDINAL, d: direction]
RETURNS [CARDINAL] =
BEGIN OPEN InlineDefs;
np: PageCount;
done: CARDINAL ← 0;
left, pos, words: CARDINAL;
IF BITAND[Pos[s], CharsPerWord - 1] # 0 THEN
ERROR StreamError[s, StreamPosition];
WHILE done # n DO
left ← n - done;
pos ← Pos[s]/CharsPerWord;
words ←
(IF d = out AND s.append THEN PageSize
ELSE (s.char + CharsPerWord - 1)/CharsPerWord) - pos;
words ← IF left > words THEN words ELSE left;
IF words # 0 THEN
BEGIN
PositionByte[s, (pos + words)*CharsPerWord, d = in];
SELECT d FROM
in => COPY[from: s.buffer.word + pos, to: a, nwords: words];
out =>
BEGIN
COPY[from: a, to: s.buffer.word + pos, nwords: words];
s.dirty ← TRUE;
END;
ENDCASE;
END;
IF s.char # CharsPerPage AND s.endof[s] AND (d = in OR ~s.append) THEN
RETURN[done + words];
np ← LOOPHOLE[left - words, CARDINAL]/PageSize;
IF left - words # 0 THEN
words ← TransferPages[s, a + words, np, d, FALSE]*PageSize + words;
a ← a + words;
done ← done + words;
ENDLOOP;
RETURN[done]
END;
-- Transfers np pages (or fewer if the file runs out while reading/updating),
-- starting at address a and the current page of the file (the one in
-- the buffer). It leaves the next page in the buffer, with the stream
-- set up at the first character. Note that if writing, the next page
-- is read, not written; if the file is extended, the buffer is cleared.
-- Returns the number of pages transferred, not counting the next one
-- that was read into the buffer. It's only legal to call TransferPages
-- when the buffer is full or empty; use TransferBlock otherwise.
-- Some special uses:
-- a=0 All transfers are into buffer (useful for positioning).
-- np=0 The current page is transfered (useful for Cleanup).
-- np=-1 Backup one page (useful for positioning).
-- The last argument is for very special uses (described below), do
-- not supply it unless you know what you are doing! If special is
-- true, the following funny things happen, depending on direction:
-- direction=in: action is made DoNothing (np should be one)
-- Used by Cleanup to skip the current page and read next one.
-- direction=out: lastAction is replaced by WriteD, and last-
-- Bytes is replaced by the numChars from the stream (np should
-- be zero). Used by Cleanup to flush with new buffer length.
TransferPages: PROCEDURE [
s: DiskHandle, a: POINTER, np: INTEGER, d: direction, special: BOOLEAN]
RETURNS [PageCount] =
BEGIN OPEN DiskDefs;
backup: BOOLEAN;
arg: DiskRequest;
i, fp, lp: PageNumber;
dobuffer: BOOLEAN ← FALSE;
DAs: POINTER TO ARRAY [0..0) OF vDA;
CAs: POINTER TO ARRAY [0..0) OF POINTER;
caa: ARRAY [0..4) OF POINTER;
daa: ARRAY [0..4) OF vDA;
f: POINTER TO FP ← @s.file.fp; -- flush the buffer if the transfer won't
IF d = in THEN IF s.dirty THEN Cleanup[s, TRUE] ELSE NULL;
-- should mark written
-- include the buffer if the transfer doesn't
IF a # NIL AND Pos[s] = CharsPerPage THEN
BEGIN -- the stream is at [page n, byte 0], but the
-- buffer is at [page n-1, byte CharsPerPage];
-- transfer the buffer, too, even if not dirty.
dobuffer ← TRUE;
np ← np + 1;
a ← a - PageSize; -- fixed below
END;
fp ← s.page;
PositionByte[s, 0, d = in];
IF backup ← (np = -1) THEN BEGIN fp ← fp - 1; np ← 0 END;
lp ← fp + np;
CAs ← LOOPHOLE[(IF np <= 1 THEN @caa ELSE Storage.Node[np + 3]) - (fp - 1)];
DAs ← LOOPHOLE[(IF np <= 1 THEN @daa ELSE Storage.Node[np + 3]) - (fp - 1)];
FOR i IN [fp..lp] DO
CAs[i] ← IF a = NIL THEN s.buffer.word ELSE a + (i - fp)*PageSize;
DAs[i] ← fillinDA;
ENDLOOP;
DAs[fp - 1] ← DAs[lp + 1] ← fillinDA;
CAs[lp] ← s.buffer.word;
IF dobuffer THEN CAs[fp] ← s.buffer.word;
InlineDefs.COPY[
from: @s.das, to: @DAs[IF backup THEN fp ELSE fp - 1],
nwords: IF backup THEN LENGTH[s.das] - 1 ELSE LENGTH[s.das]];
arg ← DiskRequest[
@CAs[0], @DAs[0], fp, lp, f, FALSE, WriteD, ReadD, FALSE, update[
BFSDefs.GetNextDA]];
IF d = in OR (d = out AND ~special AND ~s.append) THEN
BEGIN
IF d = in THEN arg.action ← ReadD;
IF special THEN arg.action ← DoNothing;
[i, s.char] ← BFSDefs.ActOnPages[LOOPHOLE[@arg]];
IF i # lp AND s.char > 0 AND CAs[i] # s.buffer.word THEN
InlineDefs.COPY[from: CAs[i], to: s.buffer.word, nwords: PageSize];
END
ELSE
BEGIN
arg.lastBytes ← IF special THEN s.char ELSE 0;
arg.lastAction ← IF special THEN WriteD ELSE ReadD;
[i, s.char] ← BFSDefs.WritePages[LOOPHOLE[@arg]];
END;
s.page ← i;
IF s.char = 0 THEN MiscDefs.Zero[s.buffer.word, PageSize];
InlineDefs.COPY[from: @DAs[i - 1], to: @s.das, nwords: LENGTH[s.das]];
IF s.das[next] = eofDA THEN
BEGIN OPEN s;
fa: FA ← FA[das[current], page, char];
SegmentDefs.UpdateFileLength[file, @fa];
END;
IF np > 1 THEN Storage.Free[CAs + fp - 1];
IF np > 1 THEN Storage.Free[DAs + fp - 1];
Setup[s, 0, s.char];
SetEnd[s, s.index = s.size];
s.dirty ← FALSE;
RETURN[i - fp - (IF dobuffer THEN 1 ELSE 0)]
END;
PositionByte: PROCEDURE [s: DiskHandle, b: CARDINAL, reading: BOOLEAN] =
BEGIN OPEN s;
pos: CARDINAL;
IF das[next] = eofDA THEN
BEGIN
IF (pos ← Pos[s]) > char AND append AND dirty THEN char ← pos;
IF b > char THEN
IF ~append OR reading THEN b ← char ELSE BEGIN char ← b; dirty ← TRUE END;
END;
Setup[s, b, char];
SetEnd[s, s.index = s.size AND char # CharsPerPage];
RETURN
END;
-- F A S T S T R E A M S
-- the counts and positions should be optimized for
-- the instruction set (as in the bcpl implementation).
Setup: PROCEDURE [s: DiskHandle, pos, end: CARDINAL] =
BEGIN OPEN InlineDefs;
mask: WORD = -s.unit;
shift: INTEGER = s.unit - 1; -- both pos and end are rounded
s.size ← BITSHIFT[BITAND[end + LOOPHOLE[shift, CARDINAL], mask], -shift];
s.index ← BITSHIFT[BITAND[pos + LOOPHOLE[shift, CARDINAL], mask], -shift];
RETURN
END;
Pos: PROCEDURE [s: DiskHandle] RETURNS [CARDINAL] =
BEGIN RETURN[InlineDefs.BITSHIFT[s.index, s.unit - 1]] END;
SetEnd: PROCEDURE [s: DiskHandle, b: BOOLEAN] =
BEGIN
g: PROCEDURE [StreamHandle] RETURNS [UNSPECIFIED];
p: PROCEDURE [StreamHandle, UNSPECIFIED];
IF s.eof # b THEN
BEGIN
s.eof ← b;
g ← s.get;
s.get ← s.savedGet;
s.savedGet ← g;
p ← s.put;
s.put ← s.savedPut;
s.savedPut ← p;
END;
RETURN
END;
ReadByte: PROCEDURE [stream: StreamHandle] RETURNS [item: UNSPECIFIED] =
BEGIN
WITH s: stream SELECT FROM
Disk =>
BEGIN
IF s.index = s.size THEN BEGIN s.getOverflow[@s]; RETURN[s.get[@s]]; END;
item ← s.buffer.byte[s.index];
s.index ← s.index + 1;
END;
ENDCASE => BEGIN SIGNAL StreamError[@s, StreamType]; item ← 0; END;
RETURN
END;
ReadWord: PROCEDURE [stream: StreamHandle] RETURNS [item: UNSPECIFIED] =
BEGIN
WITH s: stream SELECT FROM
Disk =>
BEGIN
IF s.index = s.size THEN BEGIN s.getOverflow[@s]; RETURN[s.get[@s]]; END;
item ← s.buffer.word[s.index];
s.index ← s.index + 1;
END;
ENDCASE => BEGIN SIGNAL StreamError[@s, StreamType]; item ← 0; END;
RETURN
END;
WriteByte: PROCEDURE [stream: StreamHandle, item: UNSPECIFIED] =
BEGIN
WITH s: stream SELECT FROM
Disk =>
BEGIN
IF s.index = s.size THEN
BEGIN s.putOverflow[@s]; s.put[@s, item]; RETURN; END;
s.buffer.byte[s.index] ← item;
s.index ← s.index + 1;
s.dirty ← TRUE;
END;
ENDCASE => SIGNAL StreamError[@s, StreamType];
RETURN
END;
WriteWord: PROCEDURE [stream: StreamHandle, item: UNSPECIFIED] =
BEGIN
WITH s: stream SELECT FROM
Disk =>
BEGIN
IF s.index = s.size THEN
BEGIN s.putOverflow[@s]; s.put[@s, item]; RETURN; END;
s.buffer.word[s.index] ← item;
s.index ← s.index + 1;
s.dirty ← TRUE;
END;
ENDCASE => SIGNAL StreamError[@s, StreamType];
RETURN
END;
EndOf: PROCEDURE [stream: StreamHandle] RETURNS [BOOLEAN] =
BEGIN
WITH s: stream SELECT FROM
Disk =>
BEGIN
IF s.eof THEN RETURN[TRUE];
IF s.index # s.size THEN RETURN[FALSE];
s.getOverflow[@s];
RETURN[s.endof[@s]];
END;
ENDCASE => SIGNAL StreamError[@s, StreamType];
RETURN[FALSE]
END;
END.