AbbrevExpandImpl.mesa; written by Bill Paxton, May 1981
edited by McGregor, February 14, 1983 2:06 pm
edited by Paxton, June 14, 1983 4:57 pm
This package implements abbreviation expansion in Tioga
DIRECTORY
AbbrevExpand,
Atom,
CIFS,
EditSpan,
Inline,
IO,
PutGet,
RefTab,
Rope,
RopeEdit,
RopeReader,
TextEdit,
TiogaLooks,
TiogaNode,
TiogaNodeOps;
AbbrevExpandImpl: CEDAR MONITOR
IMPORTS Atom, CIFS, EditSpan, Inline, IO, PutGet, RefTab, Rope, RopeEdit, RopeReader, TextEdit, TiogaNodeOps
EXPORTS AbbrevExpand =
BEGIN
OPEN AbbrevExpand;
***** Declarations
dictTable: RefTab.Ref ← RefTab.Create[];
Entry: TYPE = REF EntryRec;
EntryRec: TYPE = RECORD [
next: Entry,
hash: CARDINAL,
keyRope: ROPE,
node: RefTextNode,
commands: LIST OF REF ANY
];
***** Operations *****
Load: PUBLIC PROC
[fileName, dictName: ROPE,
start: Offset ← 0, len: Offset ← MaxLen,
okToMapFile: BOOLEANFALSE]
RETURNS [count: NAT] = {
rdr: RopeReader.Ref;
dict: ATOM = GetDictAtom[dictName];
root: RefBranchNode;
count ← 0;
[] ← RefTab.Store[dictTable, dict, NoDictAtom]; -- clears the dictionary
root ← PutGet.FromFile[fileName, start, len, okToMapFile];
rdr ← RopeReader.GetRopeReader[];
FOR n: Ref ← TiogaNodeOps.BranchChild[root], TiogaNodeOps.Next[n] UNTIL n=NIL DO
node: RefTextNode ← TiogaNodeOps.NarrowToTextNode[n];
IF node=NIL OR node.rope=NIL THEN LOOP;
AddToDict[root, node, dict, rdr];
count ← count+1;
ENDLOOP;
RopeReader.FreeRopeReader[rdr] };
AddToDict: ENTRY PROC [
root: RefBranchNode, node: RefTextNode, dictAtom: ATOM, rdr: RopeReader.Ref ← NIL] = {
ENABLE UNWIND => NULL;
dict: Entry ← GetDict[dictAtom];
keyRope: ROPE ← node.rope;
keyStart: Offset = 0;
hash: CARDINAL;
entry: Entry;
keyLen: NAT;
freeRdr: BOOLEANFALSE;
resultStart: Offset;
commands: LIST OF REF ANY;
IF rdr=NIL THEN { rdr ← RopeReader.GetRopeReader[]; freeRdr ← TRUE };
keyLen ← FindKeyLen[keyRope,rdr];
IF keyLen=0 THEN { IF freeRdr THEN RopeReader.FreeRopeReader[rdr]; RETURN }; -- not an entry
hash ← KeyHash[keyRope,keyStart,keyLen,rdr];
entry ← LookupInternal[dict,hash,keyRope,keyStart,keyLen,rdr];
IF entry = NIL THEN { -- add new entry
entry ← NEW[EntryRec];
IF dict # NIL THEN { entry.next ← dict.next; dict.next ← entry }
ELSE [] ← RefTab.Store[dictTable,dictAtom,entry] };
entry.hash ← hash;
entry.keyRope ← RopeEdit.Substr[keyRope,keyStart,keyLen];
entry.node ← node;
[resultStart,commands] ← ParseCommands[keyRope,keyStart+keyLen,rdr];
entry.commands ← commands;
TextEdit.DeleteText[root,node,0,resultStart];
IF freeRdr THEN RopeReader.FreeRopeReader[rdr];
};
ParseCommands: PROC [keyRope: ROPE, start: Offset, rdr: RopeReader.Ref]
RETURNS [end: Offset, commands: LIST OF REF ANY] = { OPEN RopeReader;
ENABLE ReadOffEnd => GOTO Bad;
blank: BOOLEAN;
SetPosition[rdr, keyRope, start];
IF RopeEdit.BlankChar[Peek[rdr]] THEN { blank ← TRUE; [] ← Get[rdr] } ELSE blank ← FALSE;
IF Peek[rdr] = '( THEN { -- parse command list
h: IO.Handle ← IO.CreateInputStreamFromRope[RopeEdit.Substr[keyRope,start ← GetIndex[rdr]]];
commands ← NARROW[IO.GetRefAny[h]];
start ← start+IO.GetIndex[h];
IO.Close[h];
SetPosition[rdr, keyRope, start];
IF RopeEdit.BlankChar[Peek[rdr]] THEN { blank ← TRUE; [] ← Get[rdr] } ELSE blank ← FALSE;
};
IF Peek[rdr] = '= THEN { -- check for blank
[] ← Get[rdr];
IF blank AND Peek[rdr] = ' THEN [] ← Get[rdr]; -- skip blank after = if there was one before
end ← GetIndex[rdr];
}
ELSE end ← RopeEdit.Size[keyRope];
EXITS Bad => RETURN [RopeEdit.Size[keyRope],commands];
};
Clear: PUBLIC ENTRY PROC [dictName: ROPE] = {
clear the specified abbreviation dictionary
ENABLE UNWIND => NULL;
[] ← RefTab.Store[dictTable, GetDictAtom[dictName], NIL] };
IllFormedDef: PUBLIC ERROR = CODE;
GetDictAtom: PROC [dictName: ROPE] RETURNS [dict: ATOM] = {
force dictName lowercase before create the atom
force: SAFE PROC RETURNS [c: CHAR] = CHECKED {
IF (c ← Rope.Fetch[dictName,i]) IN ['A..'Z] THEN c ← c-'A+'a;
i ← i+1 };
i: INT ← 0;
RETURN [Atom.MakeAtom[Rope.FromProc[Rope.Size[dictName],force]]];
};
Expand: PUBLIC PROC
[keyNode: RefTextNode, keyEnd: Offset, dict: ROPE, event: Event ← NIL]
RETURNS [foundIt: BOOLEAN, keyDeterminesDict: BOOLEAN, keyStart, keyLen, resultLen: Offset,
commands: LIST OF REF ANY] = {
NodeFormat: PROC [n: TiogaNode.Ref] RETURNS [TiogaNode.Name] = INLINE {
RETURN[IF n=NIL THEN TiogaNode.nullName ELSE n.format]};
FindEntry: PROC [dictAtom: ATOM] RETURNS [BOOLEAN] = {
dict: Entry ← GetDict[dictAtom];
IF dict#NIL THEN entry ← Lookup[dict,hash,keyRope,keyStart,kLen,rdr];
RETURN [entry # NIL] };
rdr: RopeReader.Ref;
dictAtom: ATOM = GetDictAtom[dict];
keyRope: ROPE ← keyNode.rope;
kLen: NAT;
kStart: Offset;
hash: CARDINAL;
entry: Entry;
foundIt ← keyDeterminesDict ← FALSE;
resultLen ← 0;
rdr ← RopeReader.GetRopeReader[];
keyEnd ← MAX[0,MIN[keyEnd,RopeEdit.Size[keyRope]]];
kStart ← keyStart ← FindKeyStart[keyRope,keyEnd,rdr];
kLen ← keyLen ← keyEnd-keyStart;
hash ← KeyHash[keyRope,keyStart,keyEnd,rdr];
see if have <dictName> '. before <keyName>
RopeReader.SetPosition[rdr,keyRope,keyStart];
IF keyStart > 0 AND RopeReader.Backwards[rdr]='. THEN {
try to get the dictName from the key
nameStart: Offset ← FindKeyStart[keyRope,keyStart-1,rdr];
IF nameStart < keyStart-1 THEN {
dictName: ATOM ← Atom.MakeAtom[RopeEdit.Substr[keyRope,nameStart,keyStart-1-nameStart]];
[] ← FindEntry[dictName];
keyDeterminesDict ← TRUE;
keyLen ← keyEnd - (keyStart ← nameStart) }};
IF entry = NIL THEN [] ← FindEntry[dictAtom];
IF entry = NIL THEN { RopeReader.FreeRopeReader[rdr]; RETURN };
{ -- do it
CheckCaps: PROC = INLINE {
allcaps ← FALSE;
RopeReader.SetPosition[rdr,keyRope,kStart];
initialcap ← RopeReader.Peek[rdr] IN ['A..'Z];
FOR i:Offset IN [0..kLen) DO
IF RopeReader.Get[rdr] NOT IN ['A..'Z] THEN RETURN;
ENDLOOP;
allcaps ← TRUE };
node: RefTextNode = entry.node;
root: RefBranchNode = TiogaNodeOps.Root[node];
keyRoot: RefBranchNode = TiogaNodeOps.Root[keyNode];
nodeRope: ROPE ← node.rope;
looks: TiogaLooks.Looks ← TextEdit.FetchLooks[keyNode,kStart];
allcaps, initialcap: BOOLEANFALSE;
format: TiogaNode.Name;
child: RefTextNode ← TiogaNodeOps.NarrowToTextNode[TiogaNodeOps.Contents[node]];
textLen: Offset ← RopeEdit.Size[nodeRope];
CheckCaps;
RopeReader.FreeRopeReader[rdr];
[,resultLen] ← TextEdit.ReplaceText[
destRoot: keyRoot, sourceRoot: root,
dest:keyNode, destStart:keyStart, destLen:keyLen,
source:node, sourceStart:0, sourceLen:textLen, event:event];
IF resultLen # textLen THEN ERROR;
IF looks # TiogaLooks.noLooks THEN
TextEdit.AddLooks[keyRoot,keyNode,looks,keyStart,textLen,event];
IF allcaps THEN TextEdit.AllCaps[keyRoot,keyNode,keyStart,textLen,event]
ELSE IF initialcap AND textLen > 0 THEN -- make first letter uppercase
TextEdit.AllCaps[keyRoot,keyNode,keyStart,1,event];
SELECT format ← NodeFormat[node] FROM
TiogaNode.nullName, NodeFormat[keyNode] => NULL;
ENDCASE => TextEdit.ChangeFormat[keyNode,format,event,keyRoot];
IF child # NIL THEN { -- insert as children of keyNode
new: TiogaNode.Span ← EditSpan.Copy[keyRoot,root,TiogaNodeOps.MakeNodeLoc[keyNode],
TiogaNodeOps.MakeNodeSpan[child,TiogaNodeOps.LastWithin[node]],
FALSE,after,1,event];
last: TiogaNode.RefTextNode ← TiogaNodeOps.NarrowToTextNode[new.end.node];
IF last # NIL AND
keyStart+textLen < TextEdit.Size[keyNode] THEN -- move text after key to end of last new node
[] ← TextEdit.MoveText[
destRoot: keyRoot, sourceRoot: keyRoot, dest: last, destLoc: TextEdit.MaxLen,
source: keyNode, start: keyStart+textLen, len: TextEdit.MaxLen, event: event] };
foundIt ← TRUE;
commands ← entry.commands;
}};
KeyHash: PROC
[keyRope: ROPE, keyStart, keyEnd: Offset, rdr: RopeReader.Ref]
RETURNS [h: CARDINAL] = {
hash must be independent of case of chars
len: NAT ← keyEnd-keyStart;
RopeReader.SetPosition[rdr,keyRope,keyStart];
h ← 0;
FOR i: NAT IN [1..MIN[3,len]) DO -- use the first 3 characters
h ← Inline.BITXOR[Inline.BITSHIFT[h,2],
RopeEdit.UpperCase[RopeReader.Get[rdr]]];
ENDLOOP;
IF len >= 2 THEN { -- use the last 2 characters
RopeReader.SetPosition[rdr,keyRope,keyEnd];
FOR i: NAT IN [len-2..len) DO
h ← Inline.BITXOR[Inline.BITSHIFT[h,2],
RopeEdit.UpperCase[RopeReader.Backwards[rdr]]];
ENDLOOP }};
Lookup: ENTRY PROC
[dict: Entry, hash: CARDINAL, keyRope: ROPE, keyStart: Offset, keyLen: NAT,
rdr1, rdr2, rdr3: RopeReader.Ref ← NIL]
RETURNS [entry: Entry] = INLINE {
ENABLE UNWIND => NULL;
RETURN [LookupInternal[dict,hash,keyRope,keyStart,keyLen,rdr1,rdr2,rdr3]] };
LookupInternal: PROC
[dict: Entry, hash: CARDINAL, keyRope: ROPE, keyStart: Offset, keyLen: NAT,
rdr1, rdr2, rdr3: RopeReader.Ref ← NIL]
RETURNS [entry: Entry] = {
FreeReaders: PROC = INLINE {
IF free1 THEN RopeReader.FreeRopeReader[rdr1];
IF free2 THEN RopeReader.FreeRopeReader[rdr2];
IF free3 THEN RopeReader.FreeRopeReader[rdr3] };
free1, free2, free3: BOOLEANFALSE;
pred: Entry ← NIL;
IF keyLen=0 THEN RETURN [NIL];
IF rdr1=NIL THEN { free1 ← TRUE; rdr1 ← RopeReader.GetRopeReader[] };
IF rdr2=NIL THEN { free2 ← TRUE; rdr2 ← RopeReader.GetRopeReader[] };
IF rdr3=NIL THEN { free3 ← TRUE; rdr3 ← RopeReader.GetRopeReader[] };
RopeReader.SetPosition[rdr1, keyRope, keyStart]; -- leave this at the start of the key
[] ← RopeReader.Peek[rdr1]; -- prime the reader for the key
entry ← NIL; -- will stay nil if don't find key in dict
FOR e: Entry ← dict, e.next UNTIL e=NIL DO
IF e.hash = hash AND RopeEdit.Size[e.keyRope] = keyLen THEN { -- check the ropes
rdr2^ ← rdr1^;
IF RopeReader.CompareSubstrs[keyRope,e.keyRope,
keyStart,keyLen,0,keyLen,rdr2,rdr3,FALSE] = equal THEN {
IF pred # NIL THEN { -- move entry to front
pred.next ← e.next;
e.next ← dict.next;
dict.next ← e };
entry ← e; EXIT }};
pred ← e;
ENDLOOP;
FreeReaders[] };
FindKeyLen: PROC [keyRope: ROPE, rdr: RopeReader.Ref] RETURNS [len: NAT] = {
size: Offset ← RopeEdit.Size[keyRope];
RopeReader.SetPosition[rdr,keyRope,0];
IF RopeEdit.PunctuationChar[RopeReader.Peek[rdr]] THEN RETURN [1];
len ← 0;
WHILE len<size AND RopeEdit.AlphaNumericChar[RopeReader.Get[rdr]] DO
len ← len+1; ENDLOOP };
FindKeyStart: PROC [keyRope: ROPE, keyEnd: Offset, rdr: RopeReader.Ref]
RETURNS [start: Offset] = {
RopeReader.SetPosition[rdr,keyRope,keyEnd];
IF RopeEdit.PunctuationChar[RopeReader.PeekBackwards[rdr]] THEN RETURN [keyEnd-1];
start ← keyEnd;
WHILE start>0 AND RopeEdit.AlphaNumericChar[RopeReader.Backwards[rdr]] DO
start ← start-1; ENDLOOP };
NoDictAtom: ATOM = $NoDictionaryAvailable;
GetDict: PROC [dictName: ATOM] RETURNS [entry: Entry] = {
prop: REF ANY;
prop ← RefTab.Fetch[dictTable,dictName].val;
WITH prop SELECT FROM
x: Entry => RETURN [x];
x: ATOM => IF x = NoDictAtom THEN RETURN [NIL] ELSE ERROR;
ENDCASE => { -- try to load it
r: ROPE ← Atom.GetPName[dictName];
fileName: ROPE ← RopeEdit.Concat[r,".Abbreviations"];
[] ← RefTab.Store[dictTable, dictName, NoDictAtom];
[] ← Load[fileName, r ! CIFS.Error => CHECKED {CONTINUE}];
RETURN [GetDict[dictName]] }};
***** Initialization
Start: PUBLIC PROC = {
};
END.