FileWriterImpl.mesa
Copyright © 1985 by Xerox Corporation. All rights reserved.
written by Paxton. March 1981
McGregor. September 10, 1982 1:15 pm
Maxwell, January 5, 1983 12:50 pm
Russ Atkinson, September 26, 1983 1:17 pm
Plass, March 1, 1985 5:43:21 pm PST
Doug Wyatt, March 3, 1985 4:36:49 pm PST
DIRECTORY
FileOps,
FileWriter,
FS,
IO,
MonitoredQueue,
PrincOpsUtils,
Rope,
RopeReader;
FileWriterImpl: CEDAR MONITOR
IMPORTS IO, FS, MonitoredQueue, PrincOpsUtils, Rope
EXPORTS FileWriter
= BEGIN OPEN FileWriter;
ROPE: TYPE = Rope.ROPE;
minBlt: NAT ~ 16;
StringPointer: PROC[ref: REF READONLY TEXT]
RETURNS[LONG POINTER] = TRUSTED INLINE {
RETURN[LOOPHOLE[ref, LONG POINTER]+SIZE[TEXT[0]]];
};
StringToStringBlt: PROC [
from: REF READONLY TEXT, fromLoc: NAT,
to: REF TEXT, toLoc: NAT,
nChars: NAT] = {
nBlt: NAT ~ (IF nChars>minBlt THEN nChars-1 ELSE 0);
IF nChars=0 THEN RETURN;
FOR i: NAT IN[nBlt..nChars) DO to[toLoc+i] ← from[fromLoc+i] ENDLOOP;
always transfer the last byte with an assignment, to get the bounds check
IF nBlt#0 THEN TRUSTED { [] ← PrincOpsUtils.ByteBlt[
to: [blockPointer: StringPointer[to],
startIndex: toLoc, stopIndexPlusOne: toLoc+nBlt],
from: [blockPointer: StringPointer[from],
startIndex: fromLoc, stopIndexPlusOne: fromLoc+nBlt]
];
};
};
OpenC: PUBLIC PROC
[capability: FS.OpenFile, start: INT ← 0, makeControl: BOOLTRUE]
RETURNS [control,comment,data: Ref] = {
stream: IO.STREAM = FS.StreamFromOpenFile[capability, $write];
IO.SetLength[stream,start];
IO.SetIndex[stream,start];
[control,comment,data] ← FinishOpen[stream,FALSE,makeControl,TRUE];
};
ToRope: PUBLIC PROC [makeControl: BOOLTRUE]
RETURNS [control,comment,data: Ref] = {
[control,comment,data] ← FinishOpen[NIL,TRUE,makeControl,FALSE];
};
ToStream: PUBLIC PROC [stream: IO.STREAM, makeControl: BOOLTRUE]
RETURNS [control,comment,data: Ref] = {
[control,comment,data] ← FinishOpen[stream,FALSE,makeControl,FALSE];
};
WriteChar: PUBLIC PROC [c: CHAR, writer: Ref] = {
loc: NAT;
block: REF TEXT ← writer.block;
IF (loc𡤋lock.length) >= blockSize THEN -- buffer is full
{ block ← BumpWriter[writer]; loc ← 0 };
block[loc] ← c; block.length ← loc+1;
};
WriteRope: PUBLIC PROC [r: ROPE, size: INT, writer: Ref, reader: RopeReader.Ref] = {
cnt: NAT;
block: REF TEXT ← writer.block;
pos: INT ← 0;
UNTIL size=0 DO
IF block.length >= blockSize THEN -- buffer is full
block ← BumpWriter[writer];
cnt ← Rope.AppendChars[buffer: block, rope: r, start: pos];
pos ← pos + cnt;
size ← size-cnt;
ENDLOOP;
};
WriteText: PUBLIC PROC [txt: REF READONLY TEXT, writer: Ref] = {
cnt, loc, txtLoc: NAT;
size: NAT ← txt.length;
block: REF TEXT ← writer.block;
txtLoc ← 0;
UNTIL size=0 DO
IF (loc𡤋lock.length) >= blockSize THEN -- buffer is full
{ block ← BumpWriter[writer]; loc ← block.length };
cnt ← MIN[blockSize-loc,size];
StringToStringBlt[txt,txtLoc,block,loc,cnt];
size ← size-cnt; txtLoc ← txtLoc+cnt; block.length ← loc+cnt;
ENDLOOP;
};
FinishOpen: PROC
[stream: IO.STREAM, toRope: BOOLFALSE, makeControl, closeStream: BOOLTRUE]
RETURNS [control,comment,data: Ref] = {
MakeOne: PROC [kind: OfWriter] RETURNS [writer: Ref] = {
writer ← GetFileWriter[];
writer.kind ← kind;
writer.block ← GetBLOCK[];
writer.stream ← stream;
writer.toRope ← toRope;
writer.closeStream ← closeStream };
IF makeControl THEN { -- make comment and control writers
control ← MakeOne[control];
FOR i:NAT IN [0..FileOps.fileIdSize) DO
WriteChar[FileOps.controlHeaderId[i],control]; ENDLOOP;
THROUGH [0..3] DO WriteChar[0C,control]; ENDLOOP; -- for length
comment ← MakeOne[comment];
FOR i:NAT IN [0..FileOps.fileIdSize) DO
WriteChar[FileOps.commentHeaderId[i],comment]; ENDLOOP;
THROUGH [0..3] DO WriteChar[0C,comment]; ENDLOOP; -- for length
};
data ← MakeOne[data];
IF ~toRope THEN { -- fork process to write data as it is produced
data.blockQueue ← GetMQ[];
data.consumer ← FORK Consumer[data];
};
};
q1, q2: MonitoredQueue.MQ;
GetMQ: ENTRY PROC RETURNS [q: MonitoredQueue.MQ] = {
ENABLE UNWIND => NULL;
IF q1 # NIL THEN { q ← q1; q1 ← NIL }
ELSE IF q2 # NIL THEN { q ← q2; q2 ← NIL }
ELSE q ← MonitoredQueue.Create[];
};
FreeMQ: ENTRY PROC [q: MonitoredQueue.MQ] = {
ENABLE UNWIND => NULL;
IF q=NIL THEN RETURN;
MonitoredQueue.Reset[q];
IF q1 = NIL THEN q1 ← q
ELSE IF q2 = NIL THEN q2 ← q;
};
fw1, fw2: Ref;
GetFileWriter: ENTRY PROC RETURNS [fw: Ref] = {
ENABLE UNWIND => NULL;
IF fw1 # NIL THEN { fw ← fw1; fw1 ← NIL }
ELSE IF fw2 # NIL THEN { fw ← fw2; fw2 ← NIL }
ELSE fw ← NEW[FileWriterBody];
IF fw.kind # unused THEN ERROR;
};
FreeFileWriter: ENTRY PROC [fw: Ref] = {
ENABLE UNWIND => NULL;
fw.block ← NIL;
fw.blockList ← NIL;
fw.blockQueue ← NIL;
fw.blockCount ← 0;
fw.stream ← NIL;
fw.kind ← unused;
IF fw1 = NIL THEN fw1 ← fw
ELSE IF fw2 = NIL THEN fw2 ← fw;
};
blks: NAT = 8;
BlockArray: TYPE = ARRAY [0..blks) OF REF TEXT;
blocks: REF BlockArray ← NEW[BlockArray];
GetBLOCK: ENTRY PROC RETURNS [b: REF TEXT] = {
ENABLE UNWIND => NULL;
FOR i:NAT IN [0..blks) DO
IF blocks[i] # NIL THEN { b ← blocks[i]; blocks[i] ← NIL; EXIT };
ENDLOOP;
IF b=NIL THEN b ← NEW[TEXT[blockSize]];
b.length ← 0;
};
FreeBLOCK: ENTRY PROC [b: REF TEXT] = {
ENABLE UNWIND => NULL;
FOR i:NAT IN [0..blks) DO
IF blocks[i] = NIL THEN { blocks[i] ← b; RETURN };
ENDLOOP;
};
BumpWriter: PUBLIC PROC [writer: Ref] RETURNS [block: REF TEXT] = {
writer.blockCount ← writer.blockCount+1; -- number added to queue/list
IF writer.kind=data AND ~writer.toRope THEN MonitoredQueue.Add[writer.block, writer.blockQueue]
ELSE writer.blockList ← NEW[BlockListBody ← [writer.block, writer.blockList]];
writer.block ← block ← GetBLOCK[];
};
Consumer: PROC [data: Ref] = { OPEN MonitoredQueue;
queue: MQ ← data.blockQueue;
stream: IO.STREAM ← data.stream;
DO x: REF ANY ← Remove[queue ! EndOfQueue => EXIT];
WITH x SELECT FROM
xx: REF TEXT => { IO.PutBlock[stream,xx]; FreeBLOCK[xx] };
ENDCASE => ERROR;
ENDLOOP;
};
Close: PUBLIC PROC [control,comment,data: Ref, textOnly: BOOL]
RETURNS [dataLen, count: INT, output: ROPE] = {
deltaBytes: PACKED ARRAY [0..3] OF CHAR;
commentStart, commentLen, controlLen: INT ← 0;
stream: IO.STREAM ← data.stream;
len, controlBlocks, commentBlocks, dataBlocks: NAT ← 0;
toRope: BOOL ← data.toRope;
controlList,commentList,dataList: BlockList;
controlBlock,commentBlock,dataBlock: REF TEXT;
Reverse: PROC [lst: BlockList] RETURNS [prev: BlockList] = {
next: BlockList;
UNTIL lst=NIL DO
next ← lst.next; lst.next ← prev; prev ← lst; lst ← next; ENDLOOP };
LastBlock: PROC [lst: BlockList] RETURNS [block: REF TEXT] = {
FOR l: BlockList ← lst, l.next DO
IF l.next=NIL THEN RETURN [l.block];
ENDLOOP };
MakeRope: PROC [first, num: NAT] RETURNS [ROPE] = {
half: NAT;
SELECT num FROM
0 => RETURN [NIL];
1 => RETURN [Rope.FromRefText[GetChars[first]]];
ENDCASE;
half ← num/2;
RETURN [Rope.Concat[MakeRope[first,half],MakeRope[first+half,num-half]]] };
FinishWriter: PROC [writer: Ref] RETURNS [list: BlockList, block: REF TEXT] = {
list ← Reverse[writer.blockList]; block ← writer.block; FreeFileWriter[writer] };
GetChars: PROC [num: NAT] RETURNS [chars: REF TEXT] = {
GetIt: PROC [blocks: NAT, list: BlockList, block: REF TEXT]
RETURNS [ok: BOOL] = {
IF num NOT IN [0..blocks) THEN RETURN [FALSE];
FOR lst: BlockList ← list, lst.next UNTIL lst=NIL DO
IF num = 0 THEN { chars ← lst.block; RETURN [TRUE] };
num ← num-1; ENDLOOP;
IF num = 0 THEN { chars ← block; RETURN [TRUE] };
ERROR };
IF GetIt[dataBlocks,dataList,dataBlock] THEN RETURN [chars];
num ← num-dataBlocks;
IF GetIt[commentBlocks,commentList,commentBlock] THEN RETURN [chars];
num ← num-commentBlocks;
IF GetIt[controlBlocks,controlList,controlBlock] THEN RETURN [chars];
ERROR };
dataBlocks ← data.blockCount;
commentStart ← dataBlocks*LONG[blockSize];
IF (len�ta.block.length) MOD 2 = 1 THEN { WriteChar[0C,data]; len ← len+1 };
commentStart ← commentStart+len;
dataBlocks ← dataBlocks+1;
IF control # NIL THEN {
IF ~textOnly THEN {
chars: REF TEXT;
WriteControlInt: PROC [int: INT] = {
deltaBytes ← LOOPHOLE[int];
FOR i:NAT IN [0..3] DO
WriteChar[deltaBytes[i],control];
ENDLOOP };
Check: PROC [writer: Ref, kind: OfWriter] = {
IF writer.kind # kind THEN ERROR;
IF writer.toRope # toRope THEN ERROR;
IF writer.closeStream # data.closeStream THEN ERROR;
IF writer.stream # data.stream THEN ERROR };
Count: PROC [list: BlockList, writer: Ref, extra: INT ← 0]
RETURNS [blocks: NAT, len: INT] = {
blocks ← writer.blockCount;
len ← LONG[blocks]*blockSize+writer.block.length+extra;
blocks ← blocks+1;
deltaBytes ← LOOPHOLE[len];
chars ← IF list=NIL THEN writer.block ELSE LastBlock[list];
FOR i:NAT IN [0..3] DO chars[FileOps.fileIdSize+i] ← deltaBytes[i]; ENDLOOP };
Check[comment,comment];
Check[control,control];
-- this is the place to write out the file props, if any
FOR i:NAT IN [0..FileOps.fileIdSize) DO -- write end trailer
WriteChar[FileOps.controlTrailerId[i],control]; ENDLOOP;
WriteControlInt[0]; -- <file-props-length>
WriteControlInt[commentStart];
IF comment.block.length MOD 2 = 1 THEN WriteChar[0C,comment]; -- fill word
[commentBlocks,commentLen] ← Count[comment.blockList,comment];
[controlBlocks,controlLen] ← Count[control.blockList,control,4]; -- add 4 for final file length
WriteControlInt[commentStart+commentLen+controlLen];
};
[controlList,controlBlock] ← FinishWriter[control];
[commentList,commentBlock] ← FinishWriter[comment] };
IF data.kind # data THEN ERROR;
dataBlock ← data.block;
IF toRope THEN {
dataList ← Reverse[data.blockList];
output ← MakeRope[0,dataBlocks+commentBlocks+controlBlocks] } -- return a balanced rope
ELSE {
MonitoredQueue.Close[data.blockQueue];
TRUSTED {JOIN data.consumer};
FreeMQ[data.blockQueue];
IO.PutBlock[stream,dataBlock];
FreeBLOCK[dataBlock];
IF control # NIL THEN {
DoBlocks: PROC [list: BlockList, block: REF TEXT] = {
FOR lst:BlockList ← list, lst.next UNTIL lst=NIL DO
IF ~textOnly THEN IO.PutBlock[stream,lst.block];
FreeBLOCK[lst.block];
ENDLOOP;
IF ~textOnly THEN IO.PutBlock[stream,block];
FreeBLOCK[block] };
DoBlocks[commentList,commentBlock];
DoBlocks[controlList,controlBlock] }};
count ← commentStart+commentLen+controlLen;
dataLen ← commentStart;
IF data.closeStream THEN IO.Close[stream];
FreeFileWriter[data];
};
END.