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];
Operations
LoadInternal:
PROC [dict:
ATOM, fileID: FileID, start: Offset ← 0, len: Offset ← MaxLen]
RETURNS [ok:
BOOL ←
FALSE, 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: BOOL ← FALSE;
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.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];
};
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: BOOL ← FALSE;
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: 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: 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: BOOL ← FALSE;
[ok: ok] ← LoadInternal[dictName, GetFileID[dictName] ! RuntimeError.UNCAUGHT => CONTINUE];
IF ok THEN RETURN [GetDict[dictName]] ELSE RETURN [NIL];
};
};
defaultSearch:
LIST
OF
ROPE ←
LIST["[]<>Commands>", "[]<>"];
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]]];
};
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];
};