TiogaAbbrevImpl.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, September 24, 1986 11:32:45 am PDT
Implements abbreviation expansion in Tioga.
DIRECTORY
Ascii USING [Lower],
Atom USING [GetPName, MakeAtom],
Basics USING [BITSHIFT, BITXOR],
BasicTime USING [GMT, nullGMT],
Convert USING [RopeFromInt],
FS USING [Error, FileInfo],
IO USING [Close, GetIndex, GetRefAny, RIS, STREAM],
MessageWindow USING [Append, Blink],
PrincOpsUtils USING [],
Process USING [GetCurrent],
RefTab USING [Create, Fetch, Pairs, Ref, Store],
Rope USING [Cat, Concat, Equal, ROPE, Size, Translate, TranslatorType],
RopeEdit USING [AlphaNumericChar, BlankChar, PunctuationChar, Substr, UpperCase],
RopeReader USING [Backwards, CompareSubstrs, FreeRopeReader, Get, GetIndex, GetRopeReader, Peek, PeekBackwards, ReadOffEnd, Ref, SetPosition],
RuntimeError USING [UNCAUGHT],
Tioga,
UserProfile USING [ListOfTokens];
TiogaAbbrevImpl: CEDAR MONITOR
IMPORTS Ascii, Atom, Convert, FS, Basics, IO, MessageWindow, RefTab, Rope, RopeEdit, RopeReader, Process, RuntimeError, Tioga, UserProfile
EXPORTS Tioga
= BEGIN
Node: TYPE = Tioga.Node;
Location: TYPE = Tioga.Location;
maxLen: INT = LAST[INT];
ROPE: TYPE = Rope.ROPE;
Looks: TYPE ~ Tioga.Looks;
noLooks: Looks ~ Tioga.noLooks;
World: TYPE ~ Tioga.World;
dictTable: RefTab.Ref ← RefTab.Create[mod: 5];
Entry: TYPE = REF EntryRec;
EntryRec:
TYPE =
RECORD [
next: Entry,
hash: CARDINAL,
keyRope: ROPE,
node: Node,
commands: LIST OF REF ANY
];
defaultSearch:
LIST
OF
ROPE ←
LIST["[]<>Commands>", "[]<>"];
fileForAbbr: RefTab.Ref ~ RefTab.Create[5];
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[];
};
LoadInternal:
PROC [dict:
ATOM, fileID: FileID, start:
INT ← 0, len:
INT ← maxLen]
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 ← Tioga.FirstChild[root], Tioga.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 ← Tioga.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;
};
LoadAbbreviations:
PUBLIC
PROC [fileName, dictName:
ROPE, start:
INT ← 0, len:
INT ← 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: Node, node: Node, dictAtom:
ATOM,
rdr: RopeReader.Ref ←
NIL] = {
ENABLE UNWIND => NULL;
dict: Entry ← GetDict[dictAtom];
keyRope: ROPE ← node.rope;
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 ← RopeEdit.Substr[keyRope,keyStart,keyLen];
entry.node ← node;
[resultStart,commands] ← ParseCommands[keyRope,keyStart+keyLen,rdr];
entry.commands ← commands;
Tioga.DeleteText[NIL, 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 RopeEdit.BlankChar[Peek[rdr]] THEN { blank ← TRUE; [] ← Get[rdr] } ELSE blank ← FALSE;
IF Peek[rdr] = '(
THEN {
-- parse command list
h: IO.STREAM ← IO.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];
};
ClearAbbreviations:
PUBLIC
ENTRY
PROC [dictName:
ROPE] = {
-- clear the specified abbreviation dictionary
ENABLE UNWIND => NULL;
[] ← RefTab.Store[dictTable, GetDictAtom[dictName], NIL];
};
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]]];
};
ExpandAbbreviation:
PUBLIC
PROC [world: World, key: Location, dict:
ROPE]
RETURNS [foundIt:
BOOL, keyDeterminesDict:
BOOL, keyStart, keyLen, resultLen:
INT, commands:
LIST
OF
REF
ANY] = {
keyNode: Node ~ key.node;
keyEnd: INT ← key.where;
rdr: RopeReader.Ref;
dictAtom: ATOM = GetDictAtom[dict];
keyRope: ROPE ← keyNode.rope;
kLen: NAT;
kStart: INT;
hash: CARDINAL;
entry: Entry;
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];
};
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: INT ← 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:
INT
IN [0..kLen)
DO
IF RopeReader.Get[rdr] NOT IN ['A..'Z] THEN RETURN;
ENDLOOP;
allcaps ← TRUE;
};
node: Node = entry.node;
root: Node = Tioga.Root[node];
keyRoot: Node = Tioga.Root[keyNode];
nodeRope: ROPE ← node.rope;
looks: Looks ← Tioga.FetchLooks[keyNode,kStart];
allcaps, initialcap: BOOL ← FALSE;
format: ATOM;
child: Node ← Tioga.FirstChild[node];
textLen: INT ← Rope.Size[nodeRope];
CheckCaps[];
RopeReader.FreeRopeReader[rdr];
[[len: resultLen]] ← Tioga.ReplaceText[world: world, destRoot: keyRoot, sourceRoot: root,
dest: [keyNode, keyStart, keyLen], source: [node, 0, textLen]];
IF resultLen#textLen THEN ERROR;
IF looks#noLooks
THEN
Tioga.ChangeTextLooks[world: world, root: keyRoot, text: [keyNode, keyStart, textLen], remove: noLooks, add: looks];
IF allcaps THEN Tioga.ChangeTextCaps[world: world, root: keyRoot, text: [keyNode, keyStart, textLen], how: allCaps]
ELSE
IF initialcap
AND textLen > 0
THEN
-- make first letter uppercase
Tioga.ChangeTextCaps[world: world, root: keyRoot, text: [keyNode, keyStart, 1], how: allCaps];
SELECT format ← Tioga.GetFormat[node]
FROM
NIL, Tioga.GetFormat[keyNode] => NULL;
ENDCASE => Tioga.SetFormat[world: world, node: keyNode, formatName: format, root: keyRoot];
IF child #
NIL
THEN {
-- insert as children of keyNode
new: Tioga.Span ← Tioga.Copy[world: world, destRoot: keyRoot, sourceRoot: root, dest: Tioga.MakeNodeLoc[keyNode], source: Tioga.MakeNodeSpan[child, Tioga.LastWithin[node]], where: after, nesting: IF textLen=0 THEN 0 ELSE 1];
last: Node ← new.end.node;
IF last #
NIL
AND keyStart+textLen < Tioga.Size[keyNode]
THEN
-- move text after key to end of last new node
[] ← Tioga.MoveText[world: world, destRoot: keyRoot, sourceRoot: keyRoot, dest: [last, maxLen], source: [keyNode, keyStart+textLen, maxLen]];
};
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],
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:
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 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:
INT, rdr: RopeReader.Ref]
RETURNS [start:
INT] = {
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: BOOL ← FALSE;
[ok: ok] ← LoadInternal[dictName, GetFileID[dictName] ! RuntimeError.UNCAUGHT => CONTINUE];
IF ok THEN RETURN [GetDict[dictName]] ELSE RETURN [NIL];
};
};
FileID: TYPE ~ REF FileIDRep;
FileIDRep:
TYPE ~
RECORD [name:
ROPE ←
NIL, 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: ROPE ← NIL;
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]]];
};
ValidateAbbreviations:
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];
};
ValidateAllAbbreviations:
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 ValidateAbbreviations[NARROW[key]] THEN changed ← TRUE;
RETURN [FALSE]
};
[] ← RefTab.Pairs[fileForAbbr, Action];
};
DoLocked[Locked];
};
END.