AbbrevExpandImpl.mesa
Copyright © 1985, 1986 by Xerox Corporation. All rights reserved.
written by Bill Paxton, May 1981
Paxton, November 8, 1982 1:31 pm
Russ Atkinson, September 26, 1983 1:21 pm
Michael Plass, May 1, 1986 11:56:28 am PDT
Rick Beach, May 30, 1985 3:02:37 pm PDT
Doug Wyatt, July 15, 1986 6:03:15 pm PDT
Implements abbreviation expansion in Tioga.
DIRECTORY
AbbrevExpand USING [Event],
AbbrevExpandExtras USING [],
Ascii USING [Lower],
Atom USING [GetPName, MakeAtom],
Basics USING [BITSHIFT, BITXOR],
BasicTime USING [GMT, nullGMT],
Convert USING [RopeFromInt],
EditSpan USING [Copy],
FS USING [Error, FileInfo],
IO USING [Close, GetIndex, GetRefAny, RIS, STREAM],
MessageWindow USING [Append, Blink],
PrincOpsUtils USING [],
Process USING [GetCurrent],
PutGet USING [FromFile],
RefTab USING [Create, Fetch, Pairs, Ref, Store],
Rope USING [Cat, Concat, Equal, MaxLen, ROPE, Size, Translate, TranslatorType],
RopeEdit USING [AlphaNumericChar, BlankChar, MaxLen, PunctuationChar, Substr, UpperCase],
RopeReader USING [Backwards, CompareSubstrs, FreeRopeReader, Get, GetIndex, GetRopeReader, Peek, PeekBackwards, ReadOffEnd, Ref, SetPosition],
RuntimeError USING [UNCAUGHT],
TextEdit USING [AddLooks, AllCaps, ChangeFormat, DeleteText, FetchLooks, MoveText, ReplaceText, Size],
TextLooks USING [Looks, noLooks],
TextNode USING [FirstChild, LastWithin, MakeNodeLoc, MakeNodeSpan, Next, NodeFormat, Offset, Ref, RefTextNode, Root, Span],
UserProfile USING [ListOfTokens];
AbbrevExpandImpl: CEDAR MONITOR
IMPORTS Ascii, Atom, Convert, EditSpan, FS, Basics, IO, MessageWindow, PutGet, RefTab, Rope, RopeEdit, RopeReader, Process, RuntimeError, TextEdit, TextNode, UserProfile
EXPORTS AbbrevExpand, AbbrevExpandExtras
= BEGIN
Declarations
RefTextNode: TYPE = TextNode.RefTextNode;
Ref: TYPE = TextNode.Ref;
Offset: TYPE = TextNode.Offset;
MaxLen: INT = LAST[INT];
ROPE: TYPE = Rope.ROPE;
Event: TYPE = AbbrevExpand.Event;
dictTable: RefTab.Ref ← RefTab.Create[mod: 5];
Entry: TYPE = REF EntryRec;
EntryRec: TYPE = RECORD [
next: Entry,
hash: CARDINAL,
keyRope: ROPE,
node: RefTextNode,
commands: LIST OF REF ANY
];
Operations
LoadInternal: PROC [dict: ATOM, fileID: FileID, start: Offset ← 0, len: Offset ← MaxLen] RETURNS [ok: BOOLFALSE, count: NAT ← 0] ~ {
rdr: RopeReader.Ref ~ RopeReader.GetRopeReader[];
root: Ref ← NIL;
Locked: PROC ~ {
[] ← RefTab.Store[dictTable, dict, NoDictAtom]; -- clears the dictionary
FOR node: Ref ← TextNode.FirstChild[root], TextNode.Next[node] UNTIL node=NIL DO
IF node=NIL OR node.rope=NIL OR node.comment THEN LOOP;
AddToDict[root, node, dict, rdr];
count ← count+1;
ENDLOOP;
[] ← RefTab.Store[fileForAbbr, dict, fileID];
};
Empty: PROC ~ {
[] ← RefTab.Store[dictTable, dict, NoDictAtom]; -- clears the dictionary
};
IF fileID = NIL THEN {DoLocked[Empty]; RETURN};
MessageWindow.Append[Rope.Cat["New ", fileID.name, " . . . "], TRUE];
root ← PutGet.FromFile[fileID.name, start, len ! FS.Error => {
MessageWindow.Append[error.explanation, FALSE];
MessageWindow.Blink[];
CONTINUE;
}];
IF root = NIL THEN RETURN;
DoLocked[Locked];
MessageWindow.Append[Convert.RopeFromInt[count], FALSE];
MessageWindow.Append[" entries ", FALSE];
RopeReader.FreeRopeReader[rdr];
ok ← TRUE;
};
Load: PUBLIC PROC [fileName, dictName: ROPE, start: Offset ← 0, len: Offset ← MaxLen]
RETURNS [count: NAT ← 0] = {
Note that fileName is ignored.
dict: ATOM = GetDictAtom[dictName];
[count: count] ← LoadInternal[dict, GetFileID[dict], start, len];
};
AddToDict: ENTRY PROC [root: Ref, 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: BOOLFALSE;
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: BOOL;
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.STREAMIO.RIS[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 ← Rope.Size[keyRope];
EXITS Bad => RETURN [Rope.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
ForceLower: Rope.TranslatorType = { RETURN [Ascii.Lower[old]] };
RETURN [Atom.MakeAtom[Rope.Translate[base~dictName, translator~ForceLower]]];
};
Expand: PUBLIC PROC [keyNode: RefTextNode, keyEnd: Offset, dict: ROPE, event: Event ← NIL]
RETURNS
[foundIt: BOOL, keyDeterminesDict: BOOL, keyStart, keyLen,
resultLen: Offset, commands: LIST OF REF ANY] = {
FindEntry: PROC [dictAtom: ATOM] RETURNS [BOOL] = {
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,Rope.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 = {
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: Ref = TextNode.Root[node];
keyRoot: Ref = TextNode.Root[keyNode];
nodeRope: ROPE ← node.rope;
looks: TextLooks.Looks ← TextEdit.FetchLooks[keyNode,kStart];
allcaps, initialcap: BOOLFALSE;
format: ATOM;
child: RefTextNode ← TextNode.FirstChild[node];
textLen: Offset ← Rope.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 # TextLooks.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 ← TextNode.NodeFormat[node] FROM
NIL, TextNode.NodeFormat[keyNode] => NULL;
ENDCASE => TextEdit.ChangeFormat[keyNode,format,event,keyRoot];
IF child # NIL THEN { -- insert as children of keyNode
new: TextNode.Span ← EditSpan.Copy[keyRoot, root, TextNode.MakeNodeLoc[keyNode], TextNode.MakeNodeSpan[child,TextNode.LastWithin[node]], after, IF textLen = 0 THEN 0 ELSE 1, event];
last: RefTextNode ← 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: MaxLen,
source: keyNode, start: keyStart+textLen, len: 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 ← Basics.BITXOR[Basics.BITSHIFT[h,2],
RopeEdit.UpperCase[RopeReader.Get[rdr]]-0C];
ENDLOOP;
IF len >= 2 THEN { -- use the last 2 characters
RopeReader.SetPosition[rdr,keyRope,keyEnd];
FOR i: NAT IN [len-2..len) DO
h ← Basics.BITXOR[Basics.BITSHIFT[h,2],
RopeEdit.UpperCase[RopeReader.Backwards[rdr]]-0C];
ENDLOOP;
};
};
Lookup: ENTRY PROC [dict: Entry, hash: CARDINAL,
keyRope: ROPE, keyStart: Offset, keyLen: NAT, rdr1, rdr2: RopeReader.Ref ← NIL]
RETURNS
[entry: Entry] = {
ENABLE UNWIND => NULL;
RETURN [LookupInternal[dict,hash,keyRope,keyStart,keyLen,rdr1,rdr2]];
};
LookupInternal: PROC [dict: Entry, hash: CARDINAL, keyRope: ROPE, keyStart: Offset, keyLen: NAT, rdr1, rdr2: RopeReader.Ref ← NIL]
RETURNS [entry: Entry] = {
FreeReaders: PROC = {
IF free1 THEN RopeReader.FreeRopeReader[rdr1];
IF free2 THEN RopeReader.FreeRopeReader[rdr2];
};
free1, free2: BOOLFALSE;
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[] };
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 Rope.Size[e.keyRope] = keyLen THEN { -- check the ropes
IF RopeReader.CompareSubstrs[keyRope,e.keyRope,
keyStart,keyLen,0,keyLen,rdr1,rdr2,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 ← Rope.Size[keyRope];
IF size=0 THEN RETURN [0];
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] = {
IF Rope.Size[keyRope] = 0 OR keyEnd = 0 THEN RETURN [0];
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
ok: BOOLFALSE;
[ok: ok] ← LoadInternal[dictName, GetFileID[dictName] ! RuntimeError.UNCAUGHT => CONTINUE];
IF ok THEN RETURN [GetDict[dictName]] ELSE RETURN [NIL];
};
};
defaultSearch: LIST OF ROPELIST["[]<>Commands>", "[]<>"];
FileID: TYPE ~ REF FileIDRep;
FileIDRep: TYPE ~ RECORD [name: ROPENIL, time: BasicTime.GMT ← BasicTime.nullGMT];
Same: PROC [a, b: FileID] RETURNS [BOOL] ~ {
RETURN [a.time = b.time AND Rope.Equal[a.name, b.name, FALSE]]
};
GetFileID: PROC [shortName: ATOM] RETURNS [FileID] ~ {
dirs: LIST OF ROPE ← UserProfile.ListOfTokens["Tioga.StyleSearchRules", defaultSearch];
name: ROPE ~ Rope.Concat[Atom.GetPName[shortName], ".abbreviations"];
fileName: ROPENIL;
created: BasicTime.GMT ← BasicTime.nullGMT;
WHILE fileName = NIL AND dirs # NIL DO
[fullFName: fileName, created: created] ← FS.FileInfo[name: name, wDir: dirs.first ! FS.Error => CONTINUE];
dirs ← dirs.rest;
ENDLOOP;
IF fileName = NIL THEN RETURN [NIL];
RETURN [NEW[FileIDRep ← [fileName, created]]];
};
abbrLockProcess: UNSAFE PROCESSNIL;
abbrLockCount: CARDINAL ← 0;
abbrLockFree: CONDITION;
DoLocked: PUBLIC PROC [action: PROC] ~ {
me: UNSAFE PROCESS ~ Process.GetCurrent[];
Lock: ENTRY PROC ~ {
UNTIL abbrLockProcess = me OR abbrLockCount = 0 DO WAIT abbrLockFree ENDLOOP;
abbrLockProcess ← me; abbrLockCount ← abbrLockCount + 1;
};
Unlock: ENTRY PROC ~ {
abbrLockCount ← abbrLockCount - 1;
IF abbrLockCount = 0 THEN {abbrLockProcess ← NIL; NOTIFY abbrLockFree};
};
Lock[];
action[ ! UNWIND => Unlock[]];
Unlock[];
};
fileForAbbr: RefTab.Ref ~ RefTab.Create[5];
ValidateAll: PUBLIC PROC RETURNS [changed: BOOLFALSE] ~ {
Called from elsewhere in Tioga when something changes that may have changed any abbr.
Does not attempt to refresh screen.
Locked: PROC ~ {
Action: PROC [key: REF, val: REF] RETURNS [quit: BOOLEAN] ~ {
IF Validate[NARROW[key]] THEN changed ← TRUE;
RETURN [FALSE]
};
[] ← RefTab.Pairs[fileForAbbr, Action];
};
DoLocked[Locked];
};
Validate: PUBLIC PROC [name: ATOM] RETURNS [changed: BOOLFALSE] ~ {
Called from elsewhere in Tioga when something changes that may have changed an abbr.
Does not attempt to refresh screen.
Locked: PROC ~ {
fileID: FileID ~ GetFileID[name];
oldFileID: FileID ~ NARROW[RefTab.Fetch[fileForAbbr, name].val];
IF oldFileID = NIL OR fileID = NIL OR Same[fileID, oldFileID]
THEN changed ← FALSE
ELSE { [] ← LoadInternal[name, fileID]; changed ← TRUE };
};
DoLocked[Locked];
};
END.