AbbrevExpandImpl.mesa
Copyright Ó 1985, 1986, 1987, 1989, 1991, 1992 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, September 24, 1991 2:06 pm PDT
Rick Beach, May 30, 1985 3:02:37 pm PDT
Spreitze, July 9, 1990 4:51 pm PDT
Willie-s, June 27, 1991 11:38 am PDT
Doug Wyatt, March 6, 1992 2:41 pm PST
DIRECTORY
AbbrevExpand USING [],
AbbrevExpandExtras USING [FindEntryProc, FindEntryVal],
Atom USING [GetPName, MakeAtom],
Basics USING [BITSHIFT, BITXOR],
CharOps USING [AlphaNumeric, Blank, Punctuation, Upper, Lower],
EditSpan USING [Copy],
IO USING [Close, GetIndex, GetRefAny, RIS, STREAM],
PFS USING [AbsoluteName, Error, FileInfo, nullUniqueID, PATH, PathFromRope, RopeFromPath, UniqueID],
PFSNames USING [Equal],
Process USING [GetCurrent],
Prop USING [PropList],
RefTab USING [Create, Fetch, Pairs, Ref, Store],
Rope USING [Cat, Concat, ROPE, Size, Substr, Translate, TranslatorType],
RopeReader USING [Backwards, CompareSubstrs, FreeRopeReader, Get, GetIndex, GetRopeReader, Peek, PeekBackwards, ReadOffEnd, Ref, SetPosition],
RuntimeError USING [UNCAUGHT],
SimpleFeedback USING [Append, Blink, PutF],
TextEdit USING [ChangeLooks, ChangeCaps, GetFormat, PutFormat, DeleteText, FetchLooks, MoveText, ReplaceText, Size],
TextEditBogus USING [GetRope],
TextNode USING [FirstChild, LastWithin, MakeNodeLoc, MakeNodeSpan, Next, Root],
Tioga USING [Node, Span, Looks, noLooks, Event],
TiogaIO USING [FromFile],
UserProfile USING [ListOfTokens];
Operations
LoadInternal:
PROC [dict:
ATOM, fileID: FileID]
RETURNS [ok:
BOOL ¬
FALSE, count:
NAT ¬ 0] ~ {
rdr: RopeReader.Ref ~ RopeReader.GetRopeReader[];
root: Node ¬ NIL;
Locked:
PROC ~ {
[] ¬ RefTab.Store[dictTable, dict, NoDictAtom]; -- clears the dictionary
FOR node: Node ¬ TextNode.FirstChild[root], TextNode.Next[node]
UNTIL node=
NIL
DO
IF node=NIL OR node.comment OR TextEdit.Size[node]=0 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};
SimpleFeedback.Append[$Tioga, begin, $Progress, Rope.Cat["New ", PFS.RopeFromPath[fileID.name], " . . . "]];
[root: root] ¬ TiogaIO.FromFile[fileID.name, fileID.uid !
PFS.Error => {
SimpleFeedback.Append[$Tioga, end, $Error, error.explanation];
SimpleFeedback.Blink[$Tioga, $Error];
CONTINUE;
};
];
IF root = NIL THEN RETURN;
DoLocked[Locked];
SimpleFeedback.PutF[$Tioga, end, $Progress, "%g entries", [integer[count]] ];
RopeReader.FreeRopeReader[rdr];
ok ¬ TRUE;
};
Load:
PUBLIC
PROC [dictName:
ROPE]
RETURNS [count:
NAT ¬ 0] = {
dict: ATOM = GetDictAtom[dictName];
[count: count] ¬ LoadInternal[dict, GetFileID[dict]];
};
AddToDict:
ENTRY
PROC [root: Node, node: Node, dictAtom:
ATOM,
rdr: RopeReader.Ref ¬
NIL] = {
ENABLE UNWIND => NULL;
dict: Entry ¬ GetDict[dictAtom];
keyRope: ROPE ¬ TextEditBogus.GetRope[node];
keyStart: INT = 0;
hash: CARDINAL;
entry: Entry;
keyLen: NAT;
freeRdr: BOOL ¬ FALSE;
resultStart: INT;
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 ¬ Rope.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:
INT, rdr: RopeReader.Ref]
RETURNS [end:
INT, commands:
LIST
OF
REF
ANY] = {
OPEN RopeReader;
ENABLE ReadOffEnd => GOTO Bad;
blank: BOOL;
SetPosition[rdr, keyRope, start];
IF CharOps.Blank[Peek[rdr]] THEN { blank ¬ TRUE; [] ¬ Get[rdr] } ELSE blank ¬ FALSE;
IF Peek[rdr] = '(
THEN {
-- parse command list
h: IO.STREAM ¬ IO.RIS[Rope.Substr[keyRope,start ¬ GetIndex[rdr]]];
commands ¬ NARROW[IO.GetRefAny[h]];
start ¬ start+IO.GetIndex[h];
IO.Close[h];
SetPosition[rdr, keyRope, start];
IF CharOps.Blank[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 [CharOps.Lower[old]] };
RETURN [Atom.MakeAtom[Rope.Translate[base~dictName, translator~ForceLower]]];
};
findEntryRegistry: Prop.PropList ¬ NIL;
ChangeRegistry:
PUBLIC
PROC [change:
PROC [Prop.PropList]
RETURNS [Prop.PropList]] ~ {
findEntryRegistry ¬ change[findEntryRegistry];
};
Expand:
PUBLIC
PROC [keyNode: Node, keyEnd:
INT, dict:
ROPE, event: Tioga.Event ¬
NIL]
RETURNS [foundIt:
BOOL, keyDeterminesDict:
BOOL, keyStart, keyLen,
resultLen:
INT, commands:
LIST
OF
REF
ANY] = {
registry: Prop.PropList ~ findEntryRegistry; -- take a snapshot
dictAtom: ATOM ~ GetDictAtom[dict];
keyRope: ROPE ¬ TextEditBogus.GetRope[keyNode];
kLen: NAT;
kStart: INT;
entry: Entry ¬ NIL;
hash: CARDINAL;
rdr: RopeReader.Ref ~ RopeReader.GetRopeReader[];
FindEntry:
PROC [dictAtom:
ATOM]
RETURNS [entry: Entry ¬
NIL] ~ {
IF registry#
NIL
THEN {
key: ROPE ~ Rope.Substr[base: keyRope, start: keyStart, len: kLen];
FOR list: Prop.PropList ¬ registry, list.rest
UNTIL entry#
NIL
OR list=
NIL
DO
WITH list.first.val
SELECT
FROM
val: AbbrevExpandExtras.FindEntryVal => {
proc: AbbrevExpandExtras.FindEntryProc ~ val;
kR: ROPE; node: Node; commands: LIST OF REF ANY;
[kR, node, commands] ¬ proc[key, dictAtom, keyDeterminesDict];
IF kR#NIL THEN entry ¬ NEW[EntryRec ¬ [
keyRope: kR, node: node, commands: commands]];
};
ENDCASE;
ENDLOOP;
};
IF entry=
NIL
THEN {
dict: Entry ~ GetDict[dictAtom];
IF dict#NIL THEN entry ¬ Lookup[dict,hash,keyRope,keyStart,kLen,rdr];
};
};
foundIt ¬ keyDeterminesDict ¬ FALSE;
resultLen ¬ 0;
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: INT ¬ FindKeyStart[keyRope,keyStart-1,rdr];
IF nameStart < keyStart-1
THEN {
dictName: ATOM ¬ Atom.MakeAtom[Rope.Substr[keyRope,nameStart,keyStart-1-nameStart]];
entry ¬ FindEntry[dictName];
keyDeterminesDict ¬ TRUE;
keyLen ¬ keyEnd - (keyStart ¬ nameStart);
};
};
IF entry = NIL THEN entry ¬ 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:
INT
IN [0..kLen)
DO
IF RopeReader.Get[rdr] NOT IN ['A..'Z] THEN RETURN;
ENDLOOP;
allcaps ¬ TRUE;
};
node: Node ~ entry.node;
child: Node ~ TextNode.FirstChild[node];
textLen: INT ~ TextEdit.Size[node];
root: Node ~ TextNode.Root[node];
keyRoot: Node = TextNode.Root[keyNode];
looks: Tioga.Looks ¬ TextEdit.FetchLooks[keyNode,kStart];
allcaps, initialcap: BOOL ¬ FALSE;
format: ATOM;
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 # Tioga.noLooks THEN TextEdit.ChangeLooks[root: keyRoot, text: keyNode,
add: looks, start: keyStart, len: textLen, event: event];
IF allcaps THEN TextEdit.ChangeCaps[root: keyRoot, dest: keyNode, start: keyStart, len: textLen, how: allCaps, event: event]
ELSE
IF initialcap
AND textLen > 0
THEN
-- make first letter uppercase
TextEdit.ChangeCaps[root: keyRoot, dest: keyNode, start: keyStart, len: 1, how: allCaps, event: event];
SELECT format ¬ TextEdit.GetFormat[node]
FROM
NIL, TextEdit.GetFormat[keyNode] => NULL;
ENDCASE => TextEdit.PutFormat[keyNode, format, event];
IF child #
NIL
THEN {
-- insert as children of keyNode
new: Tioga.Span ¬ EditSpan.Copy[keyRoot, root, TextNode.MakeNodeLoc[keyNode], TextNode.MakeNodeSpan[child,TextNode.LastWithin[node]], after, IF textLen = 0 THEN 0 ELSE 1, event];
last: Node ¬ 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:
INT, 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],
CharOps.Upper[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],
CharOps.Upper[RopeReader.Backwards[rdr]]-0C];
ENDLOOP;
};
Lookup:
ENTRY
PROC [dict: Entry, hash:
CARDINAL,
keyRope:
ROPE, keyStart:
INT, 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:
INT, 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: BOOL ¬ FALSE;
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: INT ¬ Rope.Size[keyRope];
IF size=0 THEN RETURN [0];
RopeReader.SetPosition[rdr,keyRope,0];
IF CharOps.Punctuation[RopeReader.Peek[rdr]] THEN RETURN [1];
len ¬ 0;
WHILE len<size
AND CharOps.AlphaNumeric[RopeReader.Get[rdr]]
DO
len ¬ len+1;
ENDLOOP;
FindKeyStart:
PROC [keyRope:
ROPE, keyEnd:
INT, rdr: RopeReader.Ref]
RETURNS [start:
INT] = {
IF Rope.Size[keyRope] = 0 OR keyEnd = 0 THEN RETURN [0];
RopeReader.SetPosition[rdr,keyRope,keyEnd];
IF CharOps.Punctuation[RopeReader.PeekBackwards[rdr]] THEN RETURN [keyEnd-1];
start ¬ keyEnd;
WHILE start>0
AND CharOps.AlphaNumeric[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: BOOL ¬ FALSE;
[ok: ok] ¬ LoadInternal[dictName, GetFileID[dictName] ! RuntimeError.UNCAUGHT => CONTINUE];
IF ok THEN RETURN [GetDict[dictName]] ELSE RETURN [NIL];
};
};
styleDir: ROPE ~ "/cedar/styles";
defaultSearch:
LIST
OF
ROPE ¬
LIST[styleDir];
FileID: TYPE ~ REF FileIDRep;
FileIDRep: TYPE ~ RECORD [name: PFS.PATH, uid: PFS.UniqueID];
Same:
PROC [a, b: FileID]
RETURNS [
BOOL] ~ {
RETURN [a.uid = b.uid AND PFSNames.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"];
fileNamePath: PATH ¬ NIL;
uid: PFS.UniqueID ¬ PFS.nullUniqueID;
WHILE fileNamePath =
NIL
AND dirs #
NIL
DO
[fullFName: fileNamePath, uniqueID: uid] ¬ PFS.FileInfo[name: PFS.AbsoluteName[short: PFS.PathFromRope[name], wDir: PFS.PathFromRope[dirs.first] ] ! PFS.Error => CONTINUE];
dirs ¬ dirs.rest;
ENDLOOP;
IF fileNamePath = NIL THEN RETURN [NIL];
RETURN [NEW[FileIDRep ¬ [fileNamePath, uid]]];
};
abbrLockProcess: UNSAFE PROCESS ¬ NIL;
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:
BOOL ¬
FALSE] ~ {
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:
BOOL ¬
FALSE] ~ {
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];
};