-- TiogaPGStreamsImpl.Mesa

-- last written by Pier. February 11, 1981 3:24 PM


DIRECTORY

TiogaPGStreams: FROM "TiogaPGStreams",
TiogaNode: FROM "TiogaNode" USING [ropeZone, blockZone],
FileByteStream: FROM "TiogaFBS" USING
[Handle, Open, CreateOptions, Transaction,
AccessOptions, EndOf, GetBlock, PutBlock, Close ],
InlineDefs: FROM "InlineDefs" USING [BytePair, LowHalf],
ProcessDefs: FROM "ProcessDefs" USING [Yield],
RopeInline: FROM "RopeInline" USING [Tsubstr, Tconcat],
Rope: FROM "Rope" USING [Ref, Flatten, Concat, Size];
RopeReader: FROM "RopeReader" USING [Ref, Body, GetBlock,
ToPosition];

TiogaPGStreamsImpl: PROGRAM

IMPORTS
TiogaPGStreams, nodeI: TiogaNode,
FBS: FileByteStream, InlineDefs, ProcessDefs, Rope, RopeReader
EXPORTS TiogaPGStreams =

BEGIN OPEN TiogaPGStreams;

Card: TYPE = LONG CARDINAL;

OpenPGStreams: PUBLIC PROC [filename: Rope.Ref,
transaction: FBS.Transaction ← NIL,
createOptions: FBS.CreateOptions ← none,
accessOptions: FBS.AccessOptions ← all]
RETURNS [control, text: PGStream] =
BEGIN
file: FBS.Handle;
mode: PGStreamMode;
blockSeq: BlockSeq;
pgm: PGM;
file ← FBS.Open[Rope.Flatten[filename],
transaction, createOptions, accessOptions];
control ← NEW [PGStreamBody];
text ← NEW [PGStreamBody];
control.kind ← control; text.kind ← text;
control.other ← text; text.other ← control;
control.file ← text.file ← file;
mode ← IF accessOptions=read THEN read ELSE write;
pgm ← control.pgm ← text.pgm ← CreatePGM[control.mode ← text.mode ← mode];
SELECT mode FROM
read => {
blockSeq ← text.blockSeq ← ReadTrailer[file, pgm];
control.process ← text.process ← FORK Reader[pgm, file, blockSeq]};
write => {
control.reader ← text.reader ← NEW [RopeReader.Body];
control.process ← text.process ← FORK Writer[pgm, file]};
ENDCASE => ERROR;
END;

trailerId: ARRAY [0..trailerSize) OF CHARACTER =
[0, 0, 0, 0, 212B, 311B, 234B, 232B, 360B, 214B];
trailerSize: NAT = 10;

CheckTrailerId: PROC [file: FBS.Handle] RETURNS [BOOLEAN] = {
FOR i: NAT IN [0..trailerSize) DO
IF FBS.GetChar[file] # trailerId[i] THEN RETURN[FALSE];
ENDLOOP;
RETURN [TRUE] };

CheckTrailer: PROC [file: FBS.Handle] RETURNS [ok:BOOLEAN, startControl:Card] = {
len: Card ← FBS.GetLength[file];
ok ← FALSE; startControl ← len;
IF len < trailerSize*2+4 THEN RETURN;
FBS.SetIndex[file, len-trailerSize-4];
IF ~CheckTrailerId[file] THEN RETURN;
startControl ← ReadLongCardinal[file];
IF startControl >= len-trailerSize*2-4 THEN RETURN;
FBS.SetIndex[file, startControl];
IF ~CheckTrailerId[file] THEN RETURN;
ok ← TRUE };

ReadTrailer: PROC [file: FBS.Handle, pgm: PGM] RETURNS [blockSeq: BlockSeq] = {
startControl: Card;
trailerOk: BOOLEAN;
blockNum: NAT;
[trailerOk, startControl] ← CheckTrailer[file];
blockNum ← (startControl+blockSize-1)/blockSize;
blockSeq ← NEW [BlockSeq[blockNum]];
FOR i: NAT IN [0..blockNum) DO
blockSeq[i] ← blockZone.NEW[BlockBody]; ENDLOOP;
FBS.SetIndex[file, startControl];
IF ~trailerOk THEN { -- create a dummy control block
block: Block ← blockZone.NEW[BlockBody];
style, type: Rope.Ref ← Rope.FromString["Default"];
WriteChar[fOpsI.changeStyle, control];
PutRope[style, Rope.Size[style], control, control];
WriteChar[fOpsI.startNode, control];
PutRope[type, Rope.Size[type], control, control]};
WriteChar[fOpsI.longTextNode, control];
IF startControl < 256 THEN {
WriteChar[fOpsI.shortTextNode, control];
WriteByte[startControl, control]}
ELSE IF startControl < LAST[CARDINAL] THEN {
WriteChar[fOpsI.longTextNode, control];
WriteWord[startControl, control]}
ELSE {
WriteChar[fOpsI.veryLongTextNode, control];
WriteDoubleWord[startControl, control]};
WriteByte[0, control]; -- no looksRun’s
WriteByte[fOpsI.endNode, control];
WriteByte[fOpsI.endOfFile, control];
AddToControlList[pgm, block];
};
};

Reader: PROC [pgm: PGM, file: FBS.Handle, seq: BlockSeq] =
BEGIN
UNTIL FBS.EndOf[file] DO -- read the control blocks
block: Block ← blockZone.NEW[BlockBody];
IF FBS.GetBlock[file, block] > 0 THEN AddToControlList[pgm, block];
ENDLOOP;
ControlReadsDone[pgm];
FBS.SetIndex[file, 0]; -- start of text
FOR i:NAT IN [0..seq.length) DO -- read the text blocks
[] ← FBS.GetBlock[file, seq[i]];
ENDLOOP;
TextReadsDone[pgm];
END;

Writer: PROC [pgm: PGM, file: FBS.Handle] =
BEGIN
block: Block;
DO
IF (block ← GetFromWriteList[pgm])=NIL THEN RETURN; -- writes done
FBS.PutBlock[file, block];
ENDLOOP;
END;

ClosePGStreams: PUBLIC PROC [str: PGStream] =
BEGIN
IF str.mode = write THEN
BEGIN
Flush: PROC [str: PGStream] =
{ IF str.block # NIL AND str.loc > 0 THEN WriteBlock[str]};
Flush[str]; Flush[str.other]; WritesDone[str.pgm];
END;
JOIN str.process; -- guarantees that last write (if any) is finished
FBS.Close[str.file];
END;

GetBlock: PROC [str: PGStream] RETURNS [block: Block] =
BEGIN
pgm: PGM ← str.pgm;
SELECT str.kind FROM
textBlock => {
num: NAT;
block ← pgm.blockSeq[num ← pgm.blockNum];
pgm.blockNum ← num+1; };
controlBlock => block ← GetFromControlList[pgm];
ENDCASE => ERROR;
str.block ← block; str.loc ← 0;
str.after ← IF block = NIL THEN 0 ELSE block.maxLength;
END;

ReadCharSupport: PUBLIC PROC [str: PGStream] RETURNS [CHARACTER] =
BEGIN
loc: NAT;
block: Block ← GetBlock[str];
IF (loc←str.loc) >= str.after THEN RETURN[0];
str.loc ← loc+1;
RETURN[block[loc]];
END;

SkipCharSupport: PUBLIC PROC [str: PGStream] =
BEGIN
loc: NAT;
block: Block ← GetBlock[str];
IF (loc←str.loc) < str.after THEN str.loc ← loc+1;
END;

ReadWord: PUBLIC PROC [str: PGStream] RETURNS [CARDINAL] =
BEGIN
word: InlineDefs.BytePair;
word.high ← ReadByte[str];
word.low ← ReadByte[str];
RETURN [LOOPHOLE[word, CARDINAL]];
END;

ReadDoubleWord: PROC [str: PGStream] RETURNS [Card] =
BEGIN
card: InlineDefs.LongNumber;
card.highbits ← ReadWord[str]; card.lowbits ← ReadWord[str];
RETURN [LOOPHOLE[card, CARDINAL]];
END;

MakeRope: PUBLIC PROC [len: Card, str: PGStream]
RETURNS [rope: Rope.Ref] =
BEGIN
block: Block;
loc, chunksize, after: NAT;
size: Card;
extension: Rope.Ref;
after ← str.after;
UNTIL len=0 DO
IF (loc←str.loc) >= after THEN
{ IF (block ← GetBlock[str]) = NIL THEN RETURN; -- end of file
after ← str.after; loc ← str.loc }
ELSE block ← str.block;
chunksize ← IF len >= LAST[NAT] THEN after-loc ELSE
MIN[LOOPHOLE[InlineDefs.LowHalf[len]], after-loc];
extension ← nodeI.ropeZone.NEW[
RopeInline.Tsubstr[base: block, start: loc, len: chunksize];
IF rope # NIL THEN {
rope ← nodeI.ropeZone.NEW[
RopeInline.Tconcat[base: rope, rest: extension,
size: size+chunksize, pos: size];
size ← size+chunksize }
ELSE { rope ← extension; size ← chunksize };
str.loc ← loc+chunksize; len ← len-chunksize;
ENDLOOP;
END;

WriteBlock: PROC [str: PGStream] =
BEGIN
str.block.length ← str.loc;
AddToWriteList[str.pgm, str.block];
str.block ← NIL; str.loc ← str.after ← 0;
ProcessDefs.Yield[];
END;

NewBlock: PROC [str: PGStream] RETURNS [block: Block] =
BEGIN
block ← str.block ← blockZone.NEW[BlockBody];
str.loc ← 0; str.after ← block.maxLength;
END;

WriteCharSupport: PUBLIC PROC [char: CHARACTER, str: PGStream] =
BEGIN
block: Block;
loc: NAT;
IF str.block # NIL THEN WriteBlock[str];
block ← NewBlock[str];
IF (loc←str.loc) >= str.after THEN ERROR;
str.loc ← loc+1;
block[loc] ← char;
END;

WriteWord: PUBLIC PROC [wd: CARDINAL, str: PGStream] =
BEGIN
word: InlineDefs.BytePair ← LOOPHOLE[wd];
WriteChar[LOOPHOLE[word.high, CHARACTER], str];
WriteChar[LOOPHOLE[word.low, CHARACTER], str];
END;

WriteDoubleWord: PROC [dwd: Card, str: PGStream] =
BEGIN
card: InlineDefs.LongNumber ← LOOPHOLE[dwd];
WriteWord[card.highbits, str]; WriteWord[card.lowbits, str];
END;
WriteRope: PUBLIC PROC [rope: Rope.Ref, str: PGStream] =
BEGIN
reader: RopeReader.Ref ← str.reader;
block: Block;
loc: filesI.BlockIndex;
chunksize, after: NAT;
len, pos: Card;
len ← Rope.Size[rope]; pos ← 0; after ← str.after;
UNTIL len = start DO
IF (loc←str.loc) >= after THEN
{ IF str.block # NIL THEN WriteBlock[str];
block ← NewBlock[str]; after ← str.after; loc ← str.loc }
ELSE block ← str.block;
block.length ← loc;
RopeReader.ToPosition[reader, rope, pos];
chunksize ← RopeReader.GetBlock[reader, block];
str.loc ← loc+chunksize;
pos ← pos+chunksize;
ENDLOOP;
END;

END.

LOG
added inlines for ReadChar/WriteChar. Paxton, 2/2/81
changed to use TiogaFBS. Pier 2/11