GetFileImpl.mesa
Copyright Ó 1984, 1985, 1986, 1987 by Xerox Corporation. All rights reserved.
written by Paxton. January 1981
Paxton. December 1, 1982 9:44 am
Russ Atkinson, June 18, 1984 8:29:12 pm PDT
Paul Rovner, August 10, 1983 4:28 pm
Michael Plass, March 29, 1985 2:10:32 pm PST
Rick Beach, March 28, 1985 9:46:42 am PST
Doug Wyatt, March 6, 1987 2:55:53 pm PST
DIRECTORY
Ascii USING [CR, Lower, SP, TAB],
Atom USING [MakeAtom, MakeAtomFromRefText],
BasicTime USING [GMT],
FS USING [ComponentPositions, ExpandName, GetInfo, GetName, Open, OpenFile, Position],
IO USING [PutChar, RopeFromROS, ROS, STREAM],
PGSupport USING [BadIndex, CreatePGF, EnterFormatName, EnterLooks, EnterProp, FreePGF, GetFileId, GetLength, GetTrailerLength, PGF, PutLength, RetrieveFormatName, RetrieveLooks, RetrieveProp],
Rope USING [ROPE, Size, Substr, Translate],
RopeIO USING [FromFile, FromFileC, GetRope],
RopeReader USING [Backwards, BumpIndex, Create, FreeRopeReader, Get, GetIndex, GetRope, GetRopeReader, Position, ReadOffEnd, Ref, SetIndex, SetPosition],
RuntimeError USING [UNCAUGHT],
SafeStorage USING [SetCollectionInterval],
TextLooks USING [BaseRuns, Concat, Looks, LooksBytes, Runs],
TextLooksSupport USING [NewBase],
Tioga USING [maxLen, NewNode, Node, NodeBody, ReadProp],
TiogaFile USING [comment, commentHeaderId, controlHeaderId, controlTrailerId, endNode, endOfFile, endSize, FileId, FormatIndex, look1, look2, look3, looks, looksFirst, LooksIndex, looksLast, Op, otherNode, otherNodeShort, otherNodeSpecs, otherNodeSpecsShort, prop, PropIndex, propShort, rope, runs, startNode, startNodeFirst, startNodeLast, terminalTextNode, terminalTextNodeFirst, terminalTextNodeLast],
TiogaPrivate USING [PutProp];
GetFileImpl: CEDAR MONITOR
IMPORTS Ascii, Atom, FS, IO, PGSupport, Rope, RopeIO, RopeReader, RuntimeError, SafeStorage, TextLooks, TextLooksSupport, Tioga, TiogaPrivate
EXPORTS Tioga
SHARES TextLooks
= BEGIN OPEN TiogaFile;
ROPE: TYPE = Rope.ROPE;
Byte: TYPE = [0..255];
Node: TYPE ~ Tioga.Node;
New stuff from Cedar 7.0:
BufferStrategyProc: TYPE ~ PROC [len: INT] RETURNS [
streamBufferParams: FS.StreamBufferParms, ropeBufSize: INT, ropeBuffers: INT];
BufferStrategy: BufferStrategyProc ← DefaultBufferStrategy;
Supply a new one of these to experiment with different buffering strategies;
DefaultBufferStrategy: BufferStrategyProc ~ {
streamBufferParams ← defaultStreamBufferParams;
ropeBufSize ← defaultRopeBufSize;
ropeBuffers ← defaultRopeBuffers;
};
These parameters are here to encorage experimentation. However, don't set them to unreasonable values or you will be sorry!
defaultStreamBufferParams: FS.StreamBufferParms ← [vmPagesPerBuffer: 4, nBuffers: 2];
defaultRopeBufSize: NAT ← 512;
defaultRopeBuffers: NAT ← 4;
Open: PUBLIC PROC [fileName: ROPE, start, len: INT]
RETURNS [control, comment, text: RopeReader.Ref, tiogaFile: BOOL,
fh: FS.OpenFile, createDate: BasicTime.GMT] = {
fh ← FS.Open[fileName];
[control,comment,text,tiogaFile,createDate] ← OpenC[fh,start,len];
};
OpenC: PUBLIC PROC [file: FS.OpenFile, start, len: INT] RETURNS [control, comment, text: RopeReader.Ref, tiogaFile: BOOL, createDate: BasicTime.GMT] = {
Substr: PROC [start, len: INT] RETURNS [Rope.ROPE] = {
stream: IO.STREAM ← NIL;
streamOptions: FS.StreamOptions ~ [tiogaRead: FALSE, closeFSOpenFileOnClose: FALSE];
streamBufferParams: FS.StreamBufferParms;
ropeBufSize, ropeBuffers: INT;
[streamBufferParams, ropeBufSize, ropeBuffers] ← BufferStrategy[len];
stream ← FS.StreamFromOpenFile[openFile: file, accessRights: $read,
streamOptions: streamOptions, streamBufferParms: streamBufferParams];
RETURN [RopeFile.FromStream[stream: stream, start: start, len: len, bufSize: ropeBufSize, buffers: ropeBuffers]];
};
fileLen: INT;
[bytes: fileLen, created: createDate] ← FS.GetInfo[file];
[control,comment,text,tiogaFile] ← DoOpen[fileLen,start,len,Substr];
};
DoOpen: PROC [totLen, start, len: INT, Substr: PROC [start,len: INT] RETURNS [ROPE]]
RETURNS [control, comment, text: RopeReader.Ref, tiogaFile: BOOL] = {
FakeFile: PROC = {
textRope: ROPE ← Substr[start,len];
tiogaFile ← FALSE;
RopeReader.SetPosition[text,textRope];
RopeReader.SetPosition[control,PhonyControl[len]];
{ -- for EXIT
commentStart,end,commentLen,controlLen,propsLen: INT;
endSize: NAT = FileOps.endSize;
endRope: Rope.ROPE;
tiogaFile ← TRUE;
start ← MAX[0,MIN[start,totLen]];
len ← MAX[0,MIN[len,totLen-start]];
end ← start+len;
text ← RopeReader.Create[];
comment ← RopeReader.Create[];
control ← RopeReader.Create[];
IF len <= endSize THEN GOTO FakeIt;
RopeReader.SetPosition[control,Substr[end-endSize,endSize]];
IF ~ReadControlTrailerId[control] THEN GOTO FakeIt;
IF (propsLen ← ReadLen[control]) NOT IN [0..len-endSize) THEN GOTO FakeIt;
IF (commentStart ← ReadLen[control]) NOT IN [0..len-endSize) THEN GOTO FakeIt;
IF ReadLen[control] # len THEN GOTO FakeIt;
endRope ← Substr[start+commentStart,len-commentStart]; -- comment and control
RopeReader.SetPosition[comment,endRope];
IF ~ReadCommentHeaderId[comment] THEN GOTO FakeIt;
commentLen ← ReadLen[comment];
RopeReader.SetPosition[control,endRope,commentLen];
IF ~ReadControlHeaderId[control] THEN GOTO FakeIt;
controlLen ← ReadLen[control];
IF commentStart+commentLen+controlLen # len THEN GOTO FakeIt;
RopeReader.SetPosition[text,Substr[start,commentStart]]
EXITS FakeIt => FakeFile[] }};
Open:
PUBLIC
PROC [fileName:
ROPE, start, len:
INT]
RETURNS [control, comment, text: RopeReader.Ref, tiogaFile:
BOOL, fh:
FS.OpenFile, createDate: BasicTime.
GMT] = {
fh ← FS.Open[fileName];
[control, comment, text, tiogaFile, createDate] ← OpenC[fh, start, len];
};
OpenC:
PUBLIC
PROC [file:
FS.OpenFile, start, len:
INT]
RETURNS [control, comment, text: RopeReader.Ref, tiogaFile:
BOOL, createDate: BasicTime.
GMT] = {
Substr:
PROC [start, len:
INT]
RETURNS [Rope.
ROPE] = {
RETURN [RopeIO.FromFileC[openFile: file, start: start, len: len]];
};
fileLen: INT;
[bytes: fileLen, created: createDate] ← FS.GetInfo[file];
[control, comment, text, tiogaFile] ← DoOpen[fileLen, start, len, Substr];
};
DoOpen:
PROC [size, start, len:
INT,
Substr:
PROC [start, len:
INT]
RETURNS [
ROPE]]
RETURNS [control, comment, text: RopeReader.Ref, tiogaFile:
BOOL] = {
text ← RopeReader.Create[];
comment ← RopeReader.Create[];
control ← RopeReader.Create[];
tiogaFile ← TRUE;
{
-- for EXIT
ReadId:
PROC [rdr: RopeReader.Ref, fileId: TiogaFile.FileId]
RETURNS [
BOOL] = {
get: PROC RETURNS [CHAR] ~ { RETURN[RopeReader.Get[rdr]] };
actual: TiogaFile.FileId ~ PGSupport.GetFileId[get];
RETURN [actual=fileId];
};
ReadLen:
PROC [rdr: RopeReader.Ref]
RETURNS [
INT] = {
get: PROC RETURNS [CHAR] ~ { RETURN[RopeReader.Get[rdr]] };
RETURN[PGSupport.GetTrailerLength[get]];
};
trailerLen: INT ~ TiogaFile.endSize;
propsLen, textLen, fileLen, commentLen, controlLen: INT;
commentStart, controlStart, trailerStart: INT;
start ← MAX[0, MIN[start, size]];
len ← MAX[0, MIN[len, size-start]];
IF NOT len>trailerLen THEN GOTO FakeIt;
trailerStart ← start+len-trailerLen;
RopeReader.SetPosition[control, Substr[trailerStart, trailerLen]];
IF NOT ReadId[control, TiogaFile.controlTrailerId] THEN GOTO FakeIt;
IF NOT (propsLen ← ReadLen[control]) IN [0..len-trailerLen) THEN GOTO FakeIt;
IF NOT (textLen ← ReadLen[control]) IN [0..len-trailerLen) THEN GOTO FakeIt;
IF NOT (fileLen ← ReadLen[control])=len THEN GOTO FakeIt;
commentStart ← start+textLen;
RopeReader.SetPosition[comment, Substr[commentStart, trailerStart-commentStart]];
IF NOT ReadId[comment, TiogaFile.commentHeaderId] THEN GOTO FakeIt;
commentLen ← ReadLen[comment];
controlStart ← commentStart+commentLen;
RopeReader.SetPosition[control, Substr[controlStart, trailerStart-controlStart]];
IF NOT ReadId[control, TiogaFile.controlHeaderId] THEN GOTO FakeIt;
controlLen ← ReadLen[control];
IF NOT (textLen+commentLen+controlLen)=fileLen THEN GOTO FakeIt;
RopeReader.SetPosition[text, Substr[start, textLen]];
EXITS FakeIt => {
RopeReader.SetPosition[text, Substr[start, len]];
RopeReader.SetPosition[comment, NIL];
RopeReader.SetPosition[control, PhonyControl[len]];
tiogaFile ← FALSE;
};
};
};
PhonyControl:
PROC [len:
INT]
RETURNS [
ROPE] = {
stream: IO.STREAM ~ IO.ROS[];
Put: PROC [c: CHAR] ~ { IO.PutChar[stream, c] };
Put[TiogaFile.startNode]; -- start root node
Put[VAL[0]]; -- null format for root
Put[TiogaFile.terminalTextNode];
Put[VAL[0]]; -- null format for node
Put[TiogaFile.rope]; -- rope for node
PGSupport.PutLength[Put, len]; -- length of rope for node
Put[TiogaFile.endNode]; -- end of root
Put[TiogaFile.endOfFile];
RETURN[IO.RopeFromROS[stream]];
};
FromFileError: PUBLIC ERROR = CODE;
FromFile:
PUBLIC
PROC [fileName:
ROPE, start:
INT ← 0, len:
INT ← Tioga.maxLen]
RETURNS [root: Node] = {
control, comment, stext: RopeReader.Ref;
tiogaFile: BOOL;
fh: FS.OpenFile;
createDate: BasicTime.GMT;
StartRead[];
{
ENABLE
UNWIND => EndRead[];
[control, comment, stext, tiogaFile, fh, createDate] ← Open[fileName, start, len];
root ← Finish[control, comment, stext, tiogaFile];
};
AddFileExtension[root, fh];
AddCreateDate[root, createDate];
EndRead[];
};
FromFileC:
PUBLIC
PROC [file:
FS.OpenFile, start:
INT ← 0, len:
INT ← Tioga.maxLen]
RETURNS [root: Node] = {
control, comment, stext: RopeReader.Ref;
tiogaFile: BOOL;
createDate: BasicTime.GMT;
StartRead[];
{
ENABLE
UNWIND => EndRead[];
[control, comment, stext, tiogaFile, createDate] ← OpenC[file, start, len];
root ← Finish[control, comment, stext, tiogaFile];
};
AddFileExtension[root, file];
AddCreateDate[root, createDate];
EndRead[];
};
AddFileExtension:
PROC [root: Node, file:
FS.OpenFile] = {
ForceLower:
PROC [r:
ROPE]
RETURNS [
ROPE] = {
ForceCharLower:
PROC [old:
CHAR]
RETURNS [new:
CHAR] = {
RETURN [Ascii.Lower[old]];
};
RETURN [Rope.Translate[base: r, translator: ForceCharLower]];
};
name: ROPE; cp: FS.ComponentPositions;
[fullFName: name, cp: cp] ← FS.ExpandName[name: FS.GetName[file].fullFName];
TiogaPrivate.PutProp[root, $FileExtension,
IF cp.ext.length=0
THEN $null
ELSE Atom.MakeAtom[ForceLower[name.Substr[cp.ext.start, cp.ext.length]]]];
};
AddCreateDate:
PROC [root: Node, createDate: BasicTime.
GMT] = {
TiogaPrivate.PutProp[root, $FileCreateDate,
NEW[BasicTime.GMT ← createDate]];
};
FromRope:
PUBLIC
PROC [rope:
ROPE, start:
INT ← 0, len:
INT ← Tioga.maxLen]
RETURNS [root: Node] = {
control, comment, stext: RopeReader.Ref;
tiogaFile: BOOL;
size: INT ~ Rope.Size[rope];
Substr: PROC [start, len: INT] RETURNS [ROPE] = { RETURN [Rope.Substr[rope, start, len]] };
start ← MAX[0, MIN[start, size]];
len ← MAX[0, MIN[len, size-start]];
StartRead[];
{
ENABLE
UNWIND => EndRead[];
[control, comment, stext, tiogaFile] ← DoOpen[size, start, len, Substr];
root ← Finish[control, comment, stext, tiogaFile];
};
EndRead[];
};
FromStream:
PUBLIC
PROC [stream:
IO.
STREAM, len:
INT ← Tioga.maxLen]
RETURNS [root: Node] = {
rope: ROPE ~ IO.GetRope[stream, len];
RETURN FromRope[rope];
};
collectionInterval: LONG CARDINAL;
bigCollectionInterval: LONG CARDINAL = 1000000;
StartRead:
ENTRY
PROC = {
ENABLE UNWIND => NULL;
IF startCount = 0
THEN {
collectionInterval ← SafeStorage.SetCollectionInterval[bigCollectionInterval];
};
startCount ← startCount+1;
};
EndRead:
ENTRY
PROC = {
-- restore initial collectionInterval when all Get's done
ENABLE UNWIND => NULL;
startCount ← startCount-1;
IF startCount = 0
THEN {
[] ← SafeStorage.SetCollectionInterval[collectionInterval];
};
};
Finish:
PROC [control, cmmnt, stext: RopeReader.Ref, tiogaFile:
BOOL]
RETURNS [root: Node] = {
op: Op;
parent, node, prev: Node;
terminalNode: BOOL;
textLength, runsLength: INT ← 0;
formatName: ATOM;
propname: ATOM;
charProps: REF ← NIL;
InsertNode:
PROC [node: Node] = {
IF prev#
NIL
THEN prev.next ← node
ELSE IF parent#NIL THEN parent.child ← node;
prev ← node;
};
ReadByte:
PROC
RETURNS [Byte] =
INLINE {
RETURN [LOOPHOLE[ReadChar[]]];
};
ReadChar:
PROC
RETURNS [
CHAR] = {
RETURN [RopeReader.Get[control]];
};
ReadProp:
PROC = {
specs: ROPE ← GetControlRope[];
disaster: BOOL ← FALSE;
value: REF;
value ← Tioga.ReadProp[propname, specs !
RuntimeError.UNCAUGHT => {disaster←TRUE}
];
IF disaster THEN ERROR FromFileError;
IF value #
NIL
THEN {
The character properties and character sets are not stored in the node until after the rope is stored, so that NodePropsImpl can do its consistency check.
IF propname = $CharProps THEN charProps ← value
ELSE IF propname = $CharSets THEN charSets ← value
ELSE TiogaPrivate.PutProp[node, propname, value];
};
};
GetRope:
PROC [len:
INT, rdr: RopeReader.Ref]
RETURNS [
ROPE] = {
rope: ROPE; pos: INT;
[rope, pos] ← RopeReader.Position[rdr];
RopeReader.SetIndex[rdr, pos+len];
RETURN [Rope.Substr[rope, pos, len]];
};
GetControlRope:
PROC
RETURNS [
ROPE] = {
RETURN [GetRope[ReadLength[], control]];
};
GetText:
PROC = {
len: NAT;
IF (len ← ReadByte[]) > text.maxLength THEN text ← NEW[TEXT[len]];
FOR i:
NAT
IN [0..len)
DO
text[i] ← RopeReader.Get[control];
ENDLOOP;
text.length ← len;
};
GetAtom:
PROC
RETURNS [
ATOM] = {
GetText[]; -- get the print name
RETURN [IF text.length = 0 THEN NIL ELSE Atom.MakeAtomFromRefText[text]];
};
ReadLength:
PROC
RETURNS [
INT] = {
RETURN [PGSupport.GetLength[ReadChar]];
};
NextOp:
PROC
RETURNS [op: Op] = {
op ← RopeReader.Get[control ! RopeReader.ReadOffEnd => { op ← endOfFile; CONTINUE }];
};
MAIN PROGRAM
pgf: PGSupport.PGF ← PGSupport.CreatePGF[];
text:
REF
TEXT ←
NEW[
TEXT[32]];
terminalNode ← FALSE;
op ← NextOp[];
DO
SELECT op
FROM
IN [terminalTextNodeFirst..terminalTextNodeLast] => {
formatName ← PGSupport.RetrieveFormatName[
LOOPHOLE[op-terminalTextNodeFirst, FormatIndex], pgf
! PGSupport.BadIndex => ERROR FromFileError
];
terminalNode ← TRUE;
};
IN [startNodeFirst..startNodeLast] =>
formatName ← PGSupport.RetrieveFormatName[
LOOPHOLE[op-startNodeFirst, FormatIndex], pgf
! PGSupport.BadIndex => ERROR FromFileError
];
endNode => {
IF prev#NIL THEN { prev.last ← TRUE; prev.next ← parent;};
prev ← parent;
IF (parent ← parent.next)=NIL THEN EXIT;
op ← NextOp[]; LOOP;
};
startNode => {
formatName ← GetAtom[];
[] ← PGSupport.EnterFormatName[formatName, pgf];
};
terminalTextNode => {
formatName ← GetAtom[];
[] ← PGSupport.EnterFormatName[formatName, pgf];
terminalNode ← TRUE;
};
rope, comment => {
reader: RopeReader.Ref;
IF op=rope THEN reader ← stext ELSE { reader ← cmmnt; node.comment ← TRUE;};
IF node=NIL THEN ERROR FromFileError;
IF (textLength←ReadLength[]) > 0
THEN
-- get the rope
node.rope ← GetRope[textLength, reader];
IF charProps#NIL THEN {TiogaPrivate.PutProp[node,$CharProps,charProps]; charProps←NIL};
IF charSets#NIL THEN {TiogaPrivate.PutProp[node, $CharSets, charSets]; charSets ← NIL};
SELECT runsLength
FROM
0 => NULL; -- no runs for this rope
textLength => runsLength ← 0; -- correct length
ENDCASE => ERROR FromFileError; -- mismatch
RopeReader.BumpIndex[reader, 1]; -- skip CR at end of rope
op ← NextOp[]; LOOP;
};
runs => {
-- runs, if any, come before corresponding rope
lookRuns: TextLooks.Runs;
numRuns: INT;
pos: INT ← 0;
IF node=NIL THEN ERROR FromFileError;
numRuns ← ReadLength[]; -- read number of runs
WHILE numRuns > 0
DO
num: NAT ← MIN[numRuns, LAST[NAT]];
baseRuns: TextLooks.BaseRuns ← TextLooksSupport.NewBase[num];
len: INT ← pos;
numRuns ← numRuns-num;
FOR i:
NAT
IN [0..num)
DO
-- read runs for this baseRuns
looks: TextLooks.Looks;
ReadLookChars:
PROC [num:
NAT] = {
FOR i:NAT IN [0..num) DO looks[ReadChar[]] ← TRUE; ENDLOOP;
[] ← PGSupport.EnterLooks[looks, pgf];
};
SELECT op ← NextOp[]
FROM
IN [looksFirst .. looksLast] => {
looks ← PGSupport.RetrieveLooks[
LOOPHOLE[op-looksFirst, LooksIndex], pgf
! PGSupport.BadIndex => ERROR FromFileError
]
};
look1 => ReadLookChars[1];
look2 => ReadLookChars[2];
look3 => ReadLookChars[3];
TiogaFile.looks => {
read 4 bytes of looks from control stream
lb: TextLooks.LooksBytes;
lb.byte0 ← ReadByte[]; lb.byte1 ← ReadByte[];
lb.byte2 ← ReadByte[]; lb.byte3 ← ReadByte[];
[] ← PGSupport.EnterLooks[looks ← LOOPHOLE[lb], pgf];
};
ENDCASE => ERROR FromFileError;
baseRuns[i] ← [pos←pos+ReadLength[], looks];
ENDLOOP;
lookRuns ← IF lookRuns=NIL THEN baseRuns ELSE
TextLooks.Concat[lookRuns, baseRuns, len, pos-len];
ENDLOOP;
runsLength ← pos; -- for use in checking rope length
node.runs ← lookRuns;
op ← NextOp[]; LOOP;
};
prop => {
[] ← PGSupport.EnterProp[propname ← GetAtom[], pgf];
ReadProp;
op ← NextOp[]; LOOP;
};
propShort => {
propname ← PGSupport.RetrieveProp[
LOOPHOLE[ReadByte[], PropIndex], pgf
! PGSupport.BadIndex => ERROR FromFileError
];
ReadProp;
op ← NextOp[]; LOOP;
};
otherNode, otherNodeShort, otherNodeSpecs, otherNodeSpecsShort => {
ERROR FromFileError;
};
endOfFile => {
IF parent=NIL THEN EXIT; -- have reached the root
[] ← RopeReader.Backwards[control]; -- backup so read endOfFile again
op ← endNode; LOOP;
};
ENDCASE => ERROR FromFileError;
if reach here, then want to start a new text node
IF charProps # NIL OR charSets # NIL THEN ERROR FromFileError;
InsertNode[node ← NEW[Tioga.NodeBody]];
node.formatName ← formatName;
IF terminalNode THEN terminalNode ← FALSE
ELSE { node.next ← parent; parent ← node; prev ← NIL };
op ← NextOp[];
ENDLOOP;
IF (root ← prev)=
NIL
THEN {
-- don't have a normal tree
IF node=NIL THEN root ← NEW[Tioga.NodeBody] -- null file
ELSE root ← node; -- single node in file
};
root.last ← TRUE;
TiogaPrivate.PutProp[root, $FromTiogaFile, IF tiogaFile THEN $Yes ELSE $No];
PGSupport.FreePGF[pgf];
};
ReadIndent:
PUBLIC
PROC [fileName:
ROPE, tabIndent:
NAT ← 4]
RETURNS [root: Node] = {
input: ROPE ← RopeIO.FromFile[fileName];
rdr: RopeReader.Ref ← RopeReader.GetRopeReader[];
maxOpen: NAT = 40;
openNodes: ARRAY [0..maxOpen] OF Node;
openIndents: ARRAY [0..maxOpen] OF INTEGER;
level, lvl: NAT;
indent: INTEGER;
node: Node;
noMoreInput: BOOL ← FALSE;
ReadLine:
PROC
RETURNS [
ROPE] = {
-- read line and set indent
tab: CHAR=Ascii.TAB;
space: CHAR=Ascii.SP;
cr: CHAR=Ascii.CR;
start, end: INT ← 0;
indent ← 0;
DO
SELECT RopeReader.Get[rdr ! RopeReader.ReadOffEnd => {
noMoreInput ← TRUE; EXIT }] FROM
cr => EXIT; -- blank line
tab => indent ← indent+tabIndent;
space => indent ← indent+1;
0C => { noMoreInput ← TRUE; EXIT }; -- stop at first NULL char
ENDCASE => {
-- just saw the first nonblank char on the line
start ← RopeReader.GetIndex[rdr]-1;
DO
SELECT RopeReader.Get[rdr ! RopeReader.ReadOffEnd => {
end ← RopeReader.GetIndex[rdr]; noMoreInput ← TRUE; EXIT }] FROM
cr => { end ← RopeReader.GetIndex[rdr]-1; EXIT };
0C => { end ← RopeReader.GetIndex[rdr]-1; noMoreInput ← TRUE; EXIT };
ENDCASE;
ENDLOOP;
EXIT;
};
ENDLOOP;
RETURN [Rope.Substr[input, start, end-start]];
};
PlaceNode:
PROC = {
dest: Node;
SELECT lvl FROM
< level => {
-- insert as sibling of node at lvl+1
dest ← openNodes[level ← lvl+1];
dest.last ← FALSE; node.next ← dest.next; dest.next ← node;
};
= maxOpen => {
-- insert as sibling of node at max level
dest ← openNodes[level]; dest.last ← FALSE;
node.next ← dest.next; dest.next ← node;
};
ENDCASE => {
-- increase level
dest ← openNodes[level]; node.next ← dest; dest.child ← node;
level ← level+1;
};
node.last ← TRUE; openIndents[level] ← indent; openNodes[level] ← node;
};
RopeReader.SetPosition[rdr,input,0];
openNodes[0] ← root ← Tioga.NewNode[];
root.last ← TRUE;
openIndents[0] ← -1; level ← 0;
UNTIL noMoreInput
DO
node ← Tioga.NewNode[];
IF (node.rope ← ReadLine[])=NIL THEN indent ← MAX[0,openIndents[level]];
FOR lvl ← level, lvl-1
DO
IF openIndents[lvl] < indent THEN { PlaceNode[]; EXIT };
ENDLOOP;
ENDLOOP;
RopeReader.FreeRopeReader[rdr];
};
END.