IndexTreeImpl.mesa
Copyright © 1985 by Xerox Corporation. All rights reserved.
Created by Rick Beach, July 12, 1983 3:27 pm
Rick Beach, April 29, 1985 4:17:11 pm PDT
DIRECTORY
Basics USING [Comparison],
EditSpan USING [CompareNodeOrder, Move, Place],
IndexProps USING [Compare, IndexEntry, Phrases],
IndexTree,
IO USING [PutFR, rope],
PutGet USING [FromRope],
RedBlackTree USING [Create, Compare, Delete, DuplicateKey, GetKey, Insert, Lookup3, LookupNextLarger, Table],
Rope USING [Compare, Concat, Length, Equal, ROPE],
TextNode USING [Body, FirstChild, NodeItself, Ref, Span],
TiogaButtons USING [AppendToButton, CreateButtonFromNode, TiogaButton, TiogaButtonProc, TextNodeRef, TiogaOpsRef],
TiogaOps USING [CompareNodeOrder, GetRope, Next, Parent, Ref],
TiogaOpsDefs USING []; -- convince the compiler I know what a Ref really is!
IndexTreeImpl: CEDAR PROGRAM
IMPORTS EditSpan, IO, IndexProps, PutGet, RedBlackTree, Rope, TextNode, TiogaButtons, TiogaOps
EXPORTS TiogaOpsDefs, IndexTree
= BEGIN
ROPE: TYPE = Rope.ROPE;
Comparison: TYPE = Basics.Comparison;
NodeBody: PUBLIC TYPE ~ TextNode.Body;
Index: TYPE ~ IndexTree.Index;
IxItem: TYPE ~ IndexTree.IxItem;
IxItemRec: TYPE ~ IndexTree.IxItemRec;
Table: TYPE ~ RedBlackTree.Table;
TextNodeRef: PROC [ref: REF] RETURNS [TextNode.Ref] ~ TiogaButtons.TextNodeRef;
TiogaOpsRef: PROC [ref: REF] RETURNS [TiogaOps.Ref] ~ TiogaButtons.TiogaOpsRef;
DuplicateKey: PUBLIC SIGNAL = CODE; -- if ix entry already present in the indexTable
more attention necessary to the phrases invariant here
CreateIndex: PUBLIC PROC [rootOfIndex: TextNode.Ref] RETURNS [index: Index] ~ {
index.table ← RedBlackTree.Create[getKey: IxItemGetKey, compare: IxItemCompare];
index.root ← rootOfIndex;
};
IxItemGetKey: RedBlackTree.GetKey ~ {
RETURN [data];
};
IxItemCompare: RedBlackTree.Compare ~ {
PROC [k: Key, data: UserData] RETURNS [Comparison];
ixItem1: IxItem ~ NARROW[k];
ixItem2: IxItem ~ NARROW[data];
result: Basics.Comparison ← IndexProps.Compare[ixItem1.ix, ixItem2.ix].result;
IF result # equal THEN RETURN [result];
compare see phrases
{
see1: IndexProps.Phrases ← ixItem1.ix.seePhrases;
see2: IndexProps.Phrases ← ixItem1.ix.seePhrases;
a: ROPE;
b: ROPE;
UNTIL see1 = NIL DO
a ← see1.first;
b ← IF see2 # NIL THEN see2.first ELSE NIL;
result ← Rope.Compare[a, b, FALSE];
IF result # equal THEN RETURN [result];
see1 ← see1.rest;
IF see2 # NIL THEN see2 ← see2.rest;
ENDLOOP;
IF see2 # NIL THEN RETURN [less];
};
still equal, compare order of index reference span
RETURN [CompareSpanOrder[ixItem1.range, ixItem2.range]];
};
CompareSpanOrder: PROC [span1, span2: TextNode.Span]
RETURNS [Basics.Comparison] ~ {
SELECT EditSpan.CompareNodeOrder[span1.end.node, span2.start.node] FROM
disjoint => ERROR;
before => RETURN [less];
after => RETURN [greater];
same => {
RETURN [SELECT TRUE FROM
span1.end.where < span2.start.where => less,
span1.start.where > span2.end.where => greater,
ENDCASE => equal];
};
ENDCASE => ERROR;
};
Lookup: PROC [table: Table, ixItem: IxItem]
RETURNS [small, equal, large: IxItem ← NIL] ~ {
smallerItem, equalItem, largerItem: REF;
[smallerItem, equalItem, largerItem] ← RedBlackTree.Lookup3[table, ixItem];
IF smallerItem # NIL THEN small ← NARROW[smallerItem, IxItem];
IF equalItem # NIL THEN equal ← NARROW[equalItem, IxItem];
IF largerItem # NIL THEN large ← NARROW[largerItem, IxItem];
};
InsertNewIndexEntry: PUBLIC PROCEDURE [ix: IndexProps.IndexEntry, range: TextNode.Span,
index: Index] RETURNS [ixItem: IxItem] = {
smallerIxItem, equalIxItem, largerIxItem: IxItem;
smallerParent, largerParent: TiogaOps.Ref ← NIL;
correctParent: BOOLEAN;
ixItem ← NEW[IxItemRec ← [ix, range]];
[smallerIxItem, equalIxItem, largerIxItem] ← Lookup[index.table, ixItem];
IF equalIxItem # NIL THEN {
-- perhaps we are extending the
SIGNAL DuplicateKey;
};
RedBlackTree.Insert[index.table, ixItem, ixItem !
RedBlackTree.DuplicateKey => SIGNAL DuplicateKey];
IF smallerIxItem = NIL AND largerIxItem = NIL THEN {
this entry is the first entry in the tree.
InsertPhrases[ixItem, ixItem.ix.phrases, index.root, index.root, child];
RETURN;
};
IF largerIxItem # NIL THEN {
this entry must be the first item in the tree.
[correctParent, largerParent] ← BelongsToBranch[ixItem, largerIxItem];
IF NOT correctParent AND smallerIxItem = NIL THEN {
insert a new branch for this entry.
InsertPhrases[ixItem, ixItem.ix.phrases, index.root, TextNodeRef[largerParent], before];
RETURN;
};
leave behind largerParent as a correct parent of this entry.
};
IF smallerIxItem # NIL THEN {
this entry goes somewhere after the smaller entry in the tree.
[correctParent, smallerParent] ← BelongsToBranch[ixItem, smallerIxItem];
IF NOT correctParent AND largerIxItem = NIL THEN {
insert a new branch for this entry.
InsertPhrases[ixItem, ixItem.ix.phrases, index.root, TextNodeRef[smallerParent], sibling];
RETURN;
};
leave behind smallerParent as a correct parent of this entry.
};
IF smallerParent # NIL AND AllPhrasesMatch[ixItem, smallerIxItem] THEN {
this entry belongs among the references of the smaller entry.
smallerIxItem ← BackUpToFirstItemSameAs[index.table, ixItem, smallerIxItem];
UpdateReferences[index.table, smallerIxItem, smallerIxItem.button.startLoc.node];
RETURN;
};
IF largerParent # NIL AND AllPhrasesMatch[ixItem, largerIxItem] THEN {
this entry belongs among the references of the larger entry.
smallerIxItem ← BackUpToFirstItemSameAs[index.table, ixItem, largerIxItem];
UpdateReferences[index.table, smallerIxItem, largerIxItem.button.startLoc.node];
RETURN;
};
IF largerParent = NIL THEN TRUSTED {
this is either the first child or sibling of the smaller parent.
unmatchedPhrases: IndexProps.Phrases ←
IndexProps.Compare[ixItem.ix, smallerIxItem.ix].unmatched;
InsertPhrases[ixItem, unmatchedPhrases, index.root, TextNodeRef[smallerIxItem.button.startLoc.node], IF unmatchedPhrases = NIL THEN child ELSE sibling];
RETURN;
}
ELSE IF smallerParent = NIL THEN TRUSTED {
this entry is either a first child or among the references of the larger parent.
unmatchedPhrases: IndexProps.Phrases ←
IndexProps.Compare[largerIxItem.ix, ixItem.ix].unmatched;
node: TiogaOps.Ref ← largerIxItem.button.startLoc.node;
WHILE unmatchedPhrases # NIL DO
node ← TiogaOps.Parent[node];
unmatchedPhrases ← unmatchedPhrases.rest;
ENDLOOP;
IF IndexProps.Compare[ixItem.ix, largerIxItem.ix].unmatched = NIL THEN {
UpdateReferences[index.table, ixItem, node];
RETURN;
};
unmatchedPhrases ← IndexProps.Compare[ixItem.ix, largerIxItem.ix].unmatched;
InsertPhrases[ixItem, unmatchedPhrases, index.root, TextNodeRef[node], child];
RETURN;
}
ELSE TRUSTED {
both parents exist so try climbing the path from the larger parent until it crosses over the smaller entry.
node: TiogaOps.Ref ← largerIxItem.button.startLoc.node;
where: EditSpan.Place;
unmatchedPhrases: IndexProps.Phrases ←
IndexProps.Compare[largerIxItem.ix, ixItem.ix].unmatched;
WHILE unmatchedPhrases # NIL DO
node ← TiogaOps.Parent[node];
IF TiogaOps.CompareNodeOrder[node, smallerIxItem.button.startLoc.node] = before THEN {
unmatchedPhrases: IndexProps.Phrases ←
IndexProps.Compare[smallerIxItem.ix, ixItem.ix].unmatched;
node ← smallerIxItem.button.startLoc.node;
IF unmatchedPhrases = NIL THEN {
this entry is a child of the smaller entry.
where ← child;
}
ELSE {
this entry is a sibling of some parent of the smaller entry.
WHILE unmatchedPhrases.rest # NIL DO
node ← TiogaOps.Parent[node];
unmatchedPhrases ← unmatchedPhrases.rest;
ENDLOOP;
where ← sibling;
};
unmatchedPhrases ← IndexProps.Compare[ixItem.ix, smallerIxItem.ix].unmatched;
InsertPhrases[ixItem, unmatchedPhrases, index.root, TextNodeRef[node], where];
RETURN;
};
IF IndexProps.Compare[ixItem.ix, largerIxItem.ix].unmatched = NIL THEN {
UpdateReferences[index.table, ixItem, node];
RETURN;
};
unmatchedPhrases ← unmatchedPhrases.rest;
ENDLOOP;
unmatchedPhrases ← IndexProps.Compare[ixItem.ix, largerIxItem.ix].unmatched;
InsertPhrases[ixItem, unmatchedPhrases, index.root, TextNodeRef[node], child];
RETURN;
};
};
InsertPhrases: PROCEDURE [ixItem: IxItem, phrases: IndexProps.Phrases, root, old: TextNode.Ref, where: EditSpan.Place] = {
this should be under a lock! via TiogaOps
branch: TextNode.Ref ← NIL;
UNTIL phrases = NIL OR phrases.rest = NIL DO
contents: ROPE ← phrases.first;
formatting: ROPE ← phrases.rest.first;
phraseRoot: TextNode.Ref ← PutGet.FromRope[Rope.Concat[contents, formatting]];
phraseNode: TextNode.Ref ← TextNode.FirstChild[phraseRoot];
branch ← EditSpan.Move[destRoot: root, dest: [old, TextNode.NodeItself],
sourceRoot: phraseRoot, source: [[phraseNode, TextNode.NodeItself], [phraseNode, TextNode.NodeItself]],
where: IF branch = NIL THEN where ELSE child
].start.node;
assuming that a phrase is a single node!
ixItem.button ← TiogaButtons.CreateButtonFromNode[node~TiogaOpsRef[branch], proc~HitIndexPhraseProc];
phrases ← phrases.rest.rest; -- munch on both contents and formatting parts
ENDLOOP;
IF phrases # NIL THEN ERROR; -- wonder how we got here with an invalid invariant
ixItem.button ← TiogaButtons.AppendToButton[button~ixItem.button, rope~" "];
ixItem.button ← TiogaButtons.AppendToButton[button~ixItem.button, rope~RopeForIndexReference[ixItem].reference, proc~HitIndexPhraseProc, clientData~ixItem];
};
HitIndexPhraseProc: TiogaButtons.TiogaButtonProc ~ {
get indexToolHandle from ...
normalize viewer to item
};
UpdateBranch: PROCEDURE [indexRoot: TextNode.Ref, ixItem: IxItem,
parentItem: IxItem, where: EditSpan.Place] = {
Fix up the branch that should contain the index entry.
unmatchedPhrases: IndexProps.Phrases ← IndexProps.Compare[parentItem.ix, ixItem.ix].unmatched;
node: TiogaOps.Ref ← parentItem.button.startLoc.node;
phrases: IndexProps.Phrases ← IF where = child THEN unmatchedPhrases ELSE unmatchedPhrases.rest;
WHILE phrases # NIL DO
node ← TiogaOps.Parent[node];
phrases ← phrases.rest;
ENDLOOP;
unmatchedPhrases ← IndexProps.Compare[ixItem.ix, parentItem.ix].unmatched;
InsertPhrases[ixItem, unmatchedPhrases, indexRoot, TextNodeRef[node], where];
};
UpdateReferences: PROCEDURE [indexTable: Table,
fromThisItem: IxItem, node: TiogaOps.Ref] = {
its possible for the see item reference to be dangling; should be avoided in IndexToolViewer but also checked here
referenceRope: ROPE;
sawPage, sawSee: BOOLEANFALSE;
nodeRope: ROPE ← Rope.Concat[LastPhrase[fromThisItem], ", "];
currentItem: IxItem ← fromThisItem;
DO
currentItem.button.startLoc ← [node, nodeRope.Length];
[referenceRope, sawPage, sawSee] ← RopeForIndexReference[currentItem, sawPage, sawSee];
nodeRope ← Rope.Concat[nodeRope, referenceRope];
currentItem.button.endLoc ← [node, nodeRope.Length];
currentItem ← NARROW[RedBlackTree.LookupNextLarger[indexTable, currentItem], IxItem];
IF currentItem = NIL THEN EXIT;
IF NOT AllPhrasesMatch[fromThisItem, currentItem] THEN EXIT;
ENDLOOP;
TiogaFileOps.SetContents[node, nodeRope];
};
RopeForIndexReference: PUBLIC PROCEDURE [ixItem: IxItem,
seenPage, seenSee: BOOLEANFALSE]
RETURNS [reference: ROPENIL, sawPage, sawSee: BOOLEANFALSE] = {
IF seenPage OR seenSee THEN reference ← ", ";
really want the button in here so we can update it for them
IF ixItem.ix.seePhrases # NIL THEN {
seePhrases: IndexProps.Phrases ← ixItem.ix.seePhrases;
IF sawPage THEN
reference ← Rope.Concat[reference, ", "];
this is where the various looks for each kindOfEntry are applied... $Definition "bi", $Example "z", $See "i", and so on, perhaps within a $Index property on the root of the document
IF NOT seenSee THEN
reference ← Rope.Concat[reference, IO.PutFR["%l%g%l ", IO.rope["i"], IO.rope[IF seenPage OR sawPage THEN "see also" ELSE "see"], IO.rope["I"]]];
sawSee ← seenSee;
UNTIL seePhrases = NIL DO
IF sawSee THEN reference ← Rope.Concat[reference, ", "];
reference ← Rope.Concat[reference, seePhrases.first];
seePhrases ← seePhrases.rest;
sawSee ← TRUE;
ENDLOOP;
};
};
BackUpToFirstItemSameAs: PROCEDURE [indexTable: Table, ixItem: IxItem, thisItem: IxItem]
RETURNS [firstItem: IxItem] = {
Search back in the index table for the first item that should preceed the ix index entry.
This may involve searching past several items whose phrases all match the ix entry.
smallerIxItem, equalIxItem, largerIxItem: IxItem;
firstItem ← thisItem;
DO
[smallerIxItem, equalIxItem, largerIxItem] ← Lookup[indexTable, firstItem];
IF smallerIxItem = NIL THEN EXIT;
IF NOT AllPhrasesMatch[ixItem, smallerIxItem] THEN EXIT;
firstItem ← smallerIxItem;
ENDLOOP;
RETURN [firstItem]
};
BelongsToBranch: PROCEDURE [ixItem: IxItem, try: IxItem]
RETURNS [correctParent: BOOLEAN, parent: TiogaOps.Ref] = {
Find the parent node for the ix index entry from the try index entry.
node: TiogaOps.Ref ← try.button.startLoc.node;
parent ← node;
Find the first-level parent node of node.
DO
q: TiogaOps.Ref ← TiogaOps.Parent[parent];
Quit if q is the root of the tree that contains node
IF q = NIL OR TiogaOps.Next[q] = NIL THEN EXIT;
parent ← q;
ENDLOOP;
Its the correct parent if the first phrase matches the try index entry.
correctParent ← Rope.Equal[ixItem.ix.phrases.first, TiogaOps.GetRope[parent]] OR Rope.Equal[ixItem.ix.phrases.first, try.ix.phrases.first];
};
DeleteIndexEntry: PUBLIC PROCEDURE [ixItem: IxItem, index: Index] = {
[] ← RedBlackTree.Delete[index.table, ixItem];
we should update the viewer also, hmmm...
};
LastPhrase: PROCEDURE [ixItem: IxItem] RETURNS [lastPhrase: ROPE ← NIL] = {
worry about the Phrases invariant here for sure
phrases: IndexProps.Phrases ← ixItem.ix.phrases;
UNTIL phrases = NIL DO
lastPhrase ← phrases.first;
phrases ← phrases.rest;
ENDLOOP;
};
AllPhrasesMatch: PROCEDURE [ix1, ix2: IxItem] RETURNS [BOOLEAN] = {
RETURN [IndexProps.Compare[ix1.ix, ix2.ix].result = equal];
};
END.