DIRECTORY
Char USING [Code, Set],
IO USING [PutChar, RopeFromROS, ROS, STREAM],
NodeProps USING [PutProp, DoSpecs, GetSpecs],
NodeReader,
PFS,
Prop USING [PropList],
Rope USING [Concat, FromRefText, Flatten, Length, ROPE, Substr],
Rosary USING [Concat, FromItem, ROSARY, Substr],
TextEdit,
TextNode,
Tioga USING [Looks, noLooks, PropList, Node, Location],
TiogaAccess USING [TiogaChar],
TiogaAccessPrivate USING [Reader, ReaderRep, ropePieces, Writer, WriterRep],
TiogaIO USING [FromFile, FromOpenFile, ToFile, ToOpenFile];
TiogaAccessImpl:
CEDAR
PROGRAM
IMPORTS Char, IO, NodeProps, NodeReader, PFS, Rope, Rosary, TextEdit, TextNode, TiogaIO
EXPORTS TiogaAccess
~ BEGIN
ROPE: TYPE ~ Rope.ROPE;
ROSARY: TYPE ~ Rosary.ROSARY;
TiogaChar: TYPE ~ TiogaAccess.TiogaChar;
Node: TYPE ~ Tioga.Node;
Location: TYPE ~ Tioga.Location;
Looks: TYPE ~ Tioga.Looks;
noLooks: Looks ~ Tioga.noLooks;
PropList:
TYPE ~ Tioga.PropList;
GetExternalProp:
PUBLIC
PROC [key:
ATOM, value:
REF]
RETURNS [
ROPE] ~ {
RETURN [NodeProps.GetSpecs[key, value]]
};
GetInternalProp:
PUBLIC
PROC [key:
ATOM, value:
ROPE]
RETURNS [
REF] ~ {
RETURN [NodeProps.DoSpecs[key, value]]
};
Error:
PUBLIC
ERROR [group:
PFS.ErrorGroup, expl:
ROPE] ~
CODE;
Reader: TYPE ~ TiogaAccessPrivate.Reader;
ReaderRep:
PUBLIC
TYPE ~ TiogaAccessPrivate.ReaderRep;
GetLocation:
PUBLIC
PROC [reader: Reader]
RETURNS [Location] ~ {
RETURN[[reader.node, reader.index]];
};
LocNumber:
PROC [reader: Reader, skip:
BOOL ¬
FALSE]
RETURNS [
INT] ~ {
RETURN[TextNode.LocOffset[loc1: [reader.root, 0], loc2: [reader.node, reader.index],
skipCommentNodes: skip]];
};
LocWithin:
PROC [reader: Reader, count:
INT, skip:
BOOL ¬
FALSE]
RETURNS [Location] ~ {
RETURN[TextNode.LocRelative[location: [reader.root, 0], count: count,
skipCommentNodes: skip]];
};
SetLocation:
PROC [reader: Reader, location: Location] ~ {
prevNode: Node ~ reader.node;
IF reader.putback#NIL THEN DiscardPutBack[reader];
NodeReader.Set[reader.rdr, reader.node ¬ location.node];
reader.index ¬ MIN[MAX[location.where, 0], NodeReader.Size[reader.rdr]];
IF reader.node#prevNode THEN reader.start ¬ LocNumber[reader]-reader.index;
};
SkipToNextNode:
PUBLIC
PROC [reader: Reader]
RETURNS [deltaLevel:
INT] ~ {
prevNode: Node ~ reader.node;
prevSize: INT ~ NodeReader.Size[reader.rdr];
IF reader.putback#NIL THEN DiscardPutBack[reader];
IF prevNode=NIL THEN Error[client, "Attempt to read past end of document"];
[reader.node, deltaLevel] ¬ TextNode.Forward[prevNode];
NodeReader.Set[reader.rdr, reader.node];
reader.index ¬ 0;
reader.start ¬ reader.start+prevSize+1;
};
GetLen:
PROC [reader: Reader, skip:
BOOL]
RETURNS [
INT] ~ {
loc1: Location ~ [reader.root, 0];
loc2: Location ~ TextNode.LastLocWithin[reader.root];
break: INT ~ IF (skip AND loc2.node.comment) THEN 0 ELSE 1;
RETURN[TextNode.LocOffset[loc1: loc1, loc2: loc2, skipCommentNodes: skip]+break];
};
GetLength:
PUBLIC
PROC [reader: Reader]
RETURNS [
INT] ~ {
loc1: Location ~ [reader.root, 0];
loc2: Location ~ TextNode.LastLocWithin[reader.root];
RETURN[TextNode.LocOffset[loc1: loc1, loc2: loc2, skipCommentNodes: FALSE]+1];
};
GetIndex:
PUBLIC
PROC [reader: Reader]
RETURNS [index:
INT] ~ {
RETURN[reader.start+reader.index];
};
SetIndex:
PUBLIC
PROC [reader: Reader, index:
INT] ~ {
IF reader.putback#NIL THEN DiscardPutBack[reader];
IF index
IN [reader.start..(reader.start+NodeReader.Size[reader.rdr])]
THEN reader.index ¬ index-reader.start
ELSE SetLocation[reader, LocWithin[reader, index]];
};
GetPosition:
PUBLIC
PROC [reader: Reader]
RETURNS [
INT] ~ {
RETURN[reader.offset+LocNumber[reader, TRUE]];
};
SetPosition:
PUBLIC
PROC [reader: Reader, position:
INT] ~ {
SetLocation[reader, LocWithin[reader, position-reader.offset, TRUE]];
};
FromNode:
PUBLIC
PROC [node: Node, offset:
INT ¬ 0]
RETURNS [reader: Reader] ~ {
reader ¬ NEW[ReaderRep ¬ [root: TextNode.Root[node], rdr: NodeReader.New[]]];
SetLocation[reader, [node, 0]];
reader.offset ¬ offset-LocNumber[reader, TRUE];
};
FromFile:
PUBLIC
PROC [fileName:
ROPE]
RETURNS [Reader] ~ {
RETURN [FromNode[TiogaIO.FromFile[PFS.PathFromRope[fileName]].root]]
};
FromOpenFile:
PUBLIC
PROC [openFile:
PFS.OpenFile]
RETURNS [Reader] ~ {
RETURN [FromNode[TiogaIO.FromOpenFile[openFile].root]]
};
Get:
PUBLIC
PROC [reader: Reader]
RETURNS [TiogaChar] ~ {
node: Node ~ reader.node;
index: INT ~ reader.index;
IF reader.putback#
NIL
THEN {
t: LIST OF TiogaChar ~ reader.putback;
reader.putback ¬ t.rest;
RETURN[t.first];
};
IF node=NIL THEN ERROR Error[client, "Attempt to read past end of document"];
IF index<NodeReader.Size[reader.rdr]
THEN {
info: NodeReader.CharInfo ~ NodeReader.Fetch[reader.rdr, index];
reader.index ¬ reader.index+1;
RETURN[[
charSet: Char.Set[info.char],
char: VAL[Char.Code[info.char]],
looks: info.looks,
propList: info.props,
format: node.format,
comment: node.comment,
deltaLevel: 0,
endOfNode: FALSE
]];
}
ELSE {
deltaLevel: INTEGER ~ SkipToNextNode[reader];
RETURN[[
charSet: 0,
char: '\n--reader.newline--,
looks: noLooks,
propList: node.nodeProps,
format: node.format,
comment: node.comment,
deltaLevel: deltaLevel,
endOfNode: TRUE
]];
};
};
Peek:
PUBLIC
PROC [reader: Reader]
RETURNS [TiogaChar] ~ {
node: Node ~ reader.node;
index: INT ~ reader.index;
IF reader.putback#NIL THEN RETURN[reader.putback.first];
IF node=NIL THEN ERROR Error[client, "Attempt to read past end of document"];
IF index<NodeReader.Size[reader.rdr]
THEN {
info: NodeReader.CharInfo ~ NodeReader.Fetch[reader.rdr, index];
RETURN[[
charSet: Char.Set[info.char],
char: VAL[Char.Code[info.char]],
looks: info.looks,
propList: info.props,
format: node.format,
comment: node.comment,
deltaLevel: 0,
endOfNode: FALSE
]];
}
ELSE {
deltaLevel: INTEGER ~ TextNode.Forward[node].levelDelta;
RETURN[[
charSet: 0,
char: '\n--reader.newline--,
looks: noLooks,
propList: node.nodeProps,
format: node.format,
comment: node.comment,
deltaLevel: deltaLevel,
endOfNode: TRUE
]];
};
};
PeekRope:
PUBLIC
PROC [reader: Reader]
RETURNS [rope:
ROPE ¬
NIL] ~ {
IF reader.putback#
NIL
THEN {
Putback stuff is harder than all the rest!
s: IO.STREAM ~ IO.ROS[];
FOR t:
LIST
OF TiogaChar ¬ reader.putback, t.rest
UNTIL t=
NIL
DO
IF t.first.endOfNode THEN RETURN[IO.RopeFromROS[s]];
IO.PutChar[s, t.first.char]; -- should we complain if charSet#0?
ENDLOOP;
rope ¬ IO.RopeFromROS[s];
};
IF reader.index<NodeReader.Size[reader.rdr] THEN rope ¬ Rope.Concat[rope,
Rope.Substr[base: reader.node.rope, start: reader.index]];
};
PeekRuns:
PROC [reader: Reader]
RETURNS [
ROSARY] ~ {
node: Node ~ reader.node;
IF node.runs=NIL THEN RETURN[NIL];
RETURN[Rosary.Substr[base: node.runs, start: reader.index]];
};
PeekCharSets:
PROC [reader: Reader]
RETURNS [
ROSARY] ~ {
node: Node ~ reader.node;
IF node.charSets=NIL THEN RETURN[NIL];
RETURN[Rosary.Substr[base: node.charSets, start: reader.index]];
};
PeekCharProps:
PROC [reader: Reader]
RETURNS [
ROSARY] ~ {
node: Node ~ reader.node;
IF node.charProps=NIL THEN RETURN[NIL];
RETURN[Rosary.Substr[base: node.charProps, start: reader.index]];
};
EndOf:
PUBLIC
PROC [reader: Reader]
RETURNS [
BOOLEAN] ~ {
RETURN [reader.putback=NIL AND reader.node=NIL];
};
GetNodeProps:
PUBLIC
PROC [reader: Reader]
RETURNS [PropList] ~ {
node: Node ~ reader.node;
RETURN [IF node=NIL THEN NIL ELSE node.nodeProps]
};
PutBack:
PUBLIC
PROC [reader: Reader, tiogaChar: TiogaChar] ~ {
reader.putback ¬ CONS[tiogaChar, reader.putback];
};
DiscardPutBack:
PROC [reader: Reader] ~ {
UNTIL reader.putback =
NIL
DO
t: LIST OF TiogaChar ¬ reader.putback.rest;
reader.putback.rest ¬ NIL;
reader.putback ¬ t;
ENDLOOP;
};
DoneWith:
PUBLIC
PROC [reader: Reader] ~ {
IF reader.rdr#
NIL
THEN {
NodeReader.Free[reader.rdr];
reader.rdr ¬ NIL;
DiscardPutBack[reader];
};
};
Writer: TYPE ~ TiogaAccessPrivate.Writer;
WriterRep:
PUBLIC
TYPE ~ TiogaAccessPrivate.WriterRep;
ropePieces: NAT ~ TiogaAccessPrivate.ropePieces;
textBufSize:
NAT ¬ (574-
SIZE[
TEXT[0]])*2;
Largest size that will come from the small-grain allocator (see Allocator.maxSmallBlockSize)
Create:
PUBLIC
PROC
RETURNS [Writer] ~ {
new: Writer ¬ NEW[WriterRep];
new.textBuf ¬ NEW[TEXT[textBufSize]];
Reset[new];
RETURN [new];
};
InsertRoot:
PROC [writer: Writer, x: Node ¬
NIL] ~ {
IF x =
NIL
THEN {
x ¬ TextNode.NewTextNode[];
NodeProps.PutProp[x, $NewlineDelimiter, Rope.Flatten["\n"]];
x.comment ¬ TRUE;
};
IF Rope.Length[x.rope] # 0 THEN Error[client, "Root may not contain text"];
x.parent ¬ x.next ¬ NIL;
writer.root ¬ x;
writer.last ¬ x;
writer.level ¬ writer.lastLevel ¬ 0;
};
CreateOneMoreLevel: PROC [writer: Writer] ~ {
IF writer.root = NIL THEN {
InsertRoot[writer, TextNode.NewTextNode[]];
}
ELSE {
new: TextNode.Ref ← TextNode.NewTextNode[];
new^ ← writer.root^;
writer.root.props ← NIL;
writer.root.hasstyledef ← writer.root.hasprefix ← writer.root.haspostfix ← writer.root.hascharprops ← writer.root.hascharsets ← writer.root.hasartwork ← FALSE;
new.last ← TRUE;
new.child ← writer.root;
new.next ← NIL;
writer.root.next ← new;
writer.root ← new;
writer.lastLevel ← writer.lastLevel + 1;
writer.level ← writer.level + 1;
};
};
InsertSibling:
PROC [writer: Writer, new: Tioga.Node] ~ {
x: Tioga.Node ~ writer.last;
new.next ¬ x.next;
new.parent ¬ x.parent;
x.next ¬ new;
writer.last ¬ new;
};
InsertChild:
PROC [writer: Writer, new: Tioga.Node] ~ {
x: Tioga.Node ~ writer.last;
IF x.child # NIL THEN ERROR;
new.parent ¬ x;
x.child ¬ new;
writer.last ¬ new;
writer.lastLevel ¬ writer.lastLevel + 1;
};
FoldText:
PROC [writer: Writer] ~ {
FoldRope[writer, Rope.FromRefText[writer.textBuf]];
writer.textBuf.length ¬ 0;
};
FoldRope:
PROC [writer: Writer, rope:
ROPE] ~ {
i: NAT ¬ 0;
writer.textBuf.length ¬ 0;
DO
IF writer.ropes[i] = NIL THEN {writer.ropes[i] ¬ rope; EXIT};
rope ¬ Rope.Concat[writer.ropes[i], rope];
writer.ropes[i] ¬ NIL;
IF i < ropePieces-1 THEN i ¬ i + 1;
ENDLOOP;
};
GetRope:
PROC [writer: Writer]
RETURNS [rope:
ROPE ¬
NIL] ~ {
FoldText[writer];
FOR i:
NAT
IN [0..ropePieces)
DO
IF writer.ropes[i] #
NIL
THEN {
rope ¬ Rope.Concat[writer.ropes[i], rope];
writer.ropes[i] ¬ NIL;
};
ENDLOOP;
};
FoldRosary:
PROC [rosary:
ROSARY, item:
REF, repeat, size:
INT]
RETURNS [
ROSARY] ~ {
IF repeat=0 THEN RETURN[rosary];
IF rosary=NIL AND item=NIL THEN RETURN[NIL];
IF rosary=NIL THEN rosary ¬ Rosary.FromItem[NIL, size-repeat];
RETURN[Rosary.Concat[rosary, Rosary.FromItem[item, repeat]]];
};
ConcatRosary:
PROC [r1:
ROSARY, size1:
INT, r2:
ROSARY, size2:
INT]
RETURNS [
ROSARY] ~ {
IF r1=NIL AND r2=NIL THEN RETURN[NIL];
IF r1=NIL THEN r1 ¬ Rosary.FromItem[NIL, size1];
IF r2=NIL THEN r2 ¬ Rosary.FromItem[NIL, size2];
RETURN[Rosary.Concat[r1, r2]];
};
FoldRuns:
PROC [writer: Writer] ~ {
IF writer.newLooksRepeat>0 THEN writer.runs ¬ FoldRosary[rosary: writer.runs,
item: TextEdit.ItemFromLooks[writer.newLooks], repeat: writer.newLooksRepeat,
size: writer.nodeSize];
writer.newLooks ¬ noLooks;
writer.newLooksRepeat ¬ 0;
};
GetRuns:
PROC [writer: Writer]
RETURNS [runs:
ROSARY] ~ {
FoldRuns[writer];
runs ¬ writer.runs;
writer.runs ¬ NIL;
};
FoldCharSets:
PROC [writer: Writer] ~ {
IF writer.newCharSetRepeat>0 THEN writer.charSets ¬ FoldRosary[rosary: writer.charSets,
item: TextEdit.ItemFromCharSet[writer.newCharSet], repeat: writer.newCharSetRepeat,
size: writer.nodeSize];
writer.newCharSet ¬ 0;
writer.newCharSetRepeat ¬ 0;
};
GetCharSets:
PROC [writer: Writer]
RETURNS [charSets:
ROSARY] ~ {
FoldCharSets[writer];
charSets ¬ writer.charSets;
writer.charSets ¬ NIL;
};
FoldCharProps:
PROC [writer: Writer] ~ {
IF writer.newCharPropRepeat>0 THEN writer.charProps ¬ FoldRosary[rosary: writer.charProps,
item: writer.newCharProp, repeat: writer.newCharPropRepeat,
size: writer.nodeSize];
writer.newCharProp ¬ NIL;
writer.newCharPropRepeat ¬ 0;
};
GetCharProps:
PROC [writer: Writer]
RETURNS [charProps:
ROSARY] ~ {
FoldCharProps[writer];
charProps ¬ writer.charProps;
writer.charProps ¬ NIL;
};
Put:
PUBLIC
PROC [writer: Writer, tiogaChar: TiogaChar] ~ {
IF tiogaChar.endOfNode
THEN {
new: Node ¬ TextNode.NewTextNode[];
new.format ¬ tiogaChar.format;
new.comment ¬ tiogaChar.comment;
new.rope ¬ GetRope[writer];
new.runs ¬ GetRuns[writer];
new.charSets ¬ GetCharSets[writer];
new.charProps ¬ GetCharProps[writer];
FOR l: Prop.PropList ¬ tiogaChar.propList, l.rest
UNTIL l =
NIL
DO
NodeProps.PutProp[new, NARROW[l.first.key], l.first.val];
ENDLOOP;
IF writer.root = NIL AND writer.level=0 AND tiogaChar.deltaLevel=1 AND Rope.Length[new.rope] = 0 THEN InsertRoot[writer, new]
ELSE {
IF writer.level < 1
THEN {
InsertRoot[writer];
writer.level ¬ 1;
};
WHILE writer.level < writer.lastLevel
DO
writer.last ¬ TextNode.Parent[writer.last];
writer.lastLevel ¬ writer.lastLevel - 1;
ENDLOOP;
IF writer.level = writer.lastLevel THEN InsertSibling[writer, new]
ELSE IF writer.level = writer.lastLevel + 1 THEN InsertChild[writer, new]
ELSE Error[client, "Nesting level may not increase by more than one"];
IF writer.first = NIL THEN writer.first ¬ new;
};
writer.nodeSize ¬ 0;
IF writer.lastLevel # writer.level THEN ERROR;
writer.level ¬ MAX[writer.lastLevel + tiogaChar.deltaLevel, 1];
}
ELSE {
text: REF TEXT ~ writer.textBuf;
IF tiogaChar.looks # writer.newLooks
THEN {
FoldRuns[writer];
writer.newLooks ¬ tiogaChar.looks;
};
writer.newLooksRepeat ¬ writer.newLooksRepeat + 1;
IF tiogaChar.charSet # writer.newCharSet
THEN {
FoldCharSets[writer];
writer.newCharSet ¬ tiogaChar.charSet;
};
writer.newCharSetRepeat ¬ writer.newCharSetRepeat + 1;
IF tiogaChar.propList # writer.newCharProp
THEN {
FoldCharProps[writer];
writer.newCharProp ¬ tiogaChar.propList;
};
writer.newCharPropRepeat ¬ writer.newCharPropRepeat + 1;
IF text.length = text.maxLength THEN FoldText[writer];
text[(text.length ¬ text.length+1)-1] ¬ tiogaChar.char;
writer.nodeSize ¬ writer.nodeSize + 1;
};
};
CopyNode:
PUBLIC
PROC [writer: Writer, reader: Reader, maxLength:
INT]
RETURNS [nodeEnd:
BOOLEAN] ~ {
textRope: ROPE ¬ NIL;
textRuns: ROSARY ¬ NIL;
textCharSets: ROSARY ¬ NIL;
textCharProps: ROSARY ¬ NIL;
textSize: INT ¬ 0;
WHILE maxLength > 0
AND reader.putback #
NIL
DO
tchar: TiogaChar ¬ Get[reader];
Put[writer, tchar];
maxLength ¬ maxLength - 1;
IF tchar.endOfNode THEN RETURN [TRUE];
ENDLOOP;
FoldText[writer];
FoldRuns[writer];
FoldCharSets[writer];
FoldCharProps[writer];
textRope ¬ PeekRope[reader];
textSize ¬ Rope.Length[textRope];
textRuns ¬ PeekRuns[reader];
textCharSets ¬ PeekCharSets[reader];
textCharProps ¬ PeekCharProps[reader];
nodeEnd ¬ textSize < maxLength;
IF
NOT nodeEnd
THEN {
textRope ¬ Rope.Substr[textRope, 0, maxLength];
textSize ¬ Rope.Length[textRope];
IF textRuns # NIL THEN textRuns ¬ Rosary.Substr[textRuns, 0, textSize];
IF textCharSets # NIL THEN textCharSets ¬ Rosary.Substr[textCharSets, 0, textSize];
IF textCharProps # NIL THEN textCharSets ¬ Rosary.Substr[textCharProps, 0, textSize];
};
FoldRope[writer, textRope];
writer.runs ¬ ConcatRosary[writer.runs, writer.nodeSize, textRuns, textSize];
writer.charSets ¬ ConcatRosary[writer.charSets, writer.nodeSize, textCharSets, textSize];
writer.charProps ¬ ConcatRosary[writer.charProps, writer.nodeSize, textCharProps, textSize];
reader.index ¬ reader.index + textSize;
writer.nodeSize ¬ writer.nodeSize + textSize;
IF nodeEnd THEN Put[writer, Get[reader]];
};
Nest:
PUBLIC
PROC [writer: Writer, delta:
INT] ~ {
level: INT ~ writer.level + delta;
IF level > writer.lastLevel+1 THEN Error[client, "Nesting level may not increase by more than one"];
writer.level ¬ level;
};
FinishWrite:
PUBLIC
PROC [writer: Writer, action:
PROC [root, first, last: Node]] ~ {
Collects all the pieces together.
IF writer.nodeSize>0
THEN Put[writer, [
charSet: 0,
char: '\n,
looks: ALL[FALSE],
format: NIL,
comment: FALSE,
endOfNode: TRUE,
deltaLevel: 0,
propList: NIL
]];
action[writer.root, writer.first, writer.last];
Reset[writer];
};
WriteFile:
PUBLIC
PROC [writer: Writer, fileName:
ROPE] ~ {
path: PFS.PATH ~ PFS.PathFromRope[fileName];
action: PROC [root, first, last: Node] ~ { [] ¬ TiogaIO.ToFile[path, root] };
FinishWrite[writer, action];
};
WriteOpenFile:
PUBLIC
PROC [writer: Writer, openFile:
PFS.OpenFile] ~ {
action: PROC [root, first, last: Node] ~ { [] ¬ TiogaIO.ToOpenFile[openFile, root] };
FinishWrite[writer, action];
};
WriteReader:
PUBLIC
PROC [writer: Writer]
RETURNS [reader: Reader] ~ {
action: PROC [root, first, last: Node] ~ { reader ¬ FromNode[root] };
FinishWrite[writer, action];
};
WriteNode:
PUBLIC
PROC [writer: Writer]
RETURNS [node: Node] ~ {
action: PROC [root, first, last: Node] ~ { node ¬ root; writer.root ¬ NIL };
FinishWrite[writer, action];
};
Reset:
PUBLIC
PROC [writer: Writer] ~ {
IF writer.root #
NIL
THEN {
TEditInput.FreeTree[writer.root];
writer.root ¬ NIL;
};
writer.first ¬ writer.last ¬ NIL;
writer.level ¬ 0;
writer.lastLevel ¬ 0;
writer.nodeSize ¬ 0;
writer.ropes ¬ ALL[NIL];
writer.textBuf.length ¬ 0;
writer.runs ¬ NIL;
writer.newLooks ¬ noLooks;
writer.newLooksRepeat ¬ 0;
writer.charSets ¬ NIL;
writer.newCharSet ¬ 0;
writer.newCharSetRepeat ¬ 0;
writer.charProps ¬ NIL;
writer.newCharProp ¬ NIL;
writer.newCharPropRepeat ¬ 0;
};