<> <> <> <> <> <> <> <> <> DIRECTORY AbbrevExpand USING [Event], 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], TextLooks USING [Looks, noLooks], Tioga USING [AddLooks, AllCaps, ChangeFormat, Copy, DeleteText, FetchLooks, FirstChild, FromFile, LastWithin, MakeNodeLoc, MakeNodeSpan, MoveText, Next, Node, NodeFormat, ReplaceText, Root, Size, Span, World], UserProfile USING [ListOfTokens]; AbbrevExpandImpl: CEDAR MONITOR IMPORTS Ascii, Atom, Convert, EditSpan, FS, Basics, IO, MessageWindow, RefTab, Rope, RopeEdit, RopeReader, Process, RuntimeError, Tioga, UserProfile EXPORTS AbbrevExpand = BEGIN <> Node: TYPE = Tioga.Node; maxLen: INT = LAST[INT]; ROPE: TYPE = Rope.ROPE; 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 ]; <> 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; }; Load: PUBLIC PROC [fileName, dictName: ROPE, start: INT _ 0, len: INT _ maxLen] RETURNS [count: NAT _ 0] = { <> 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[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]; }; 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 [world: World, keyNode: Node, keyEnd: INT, dict: ROPE] RETURNS [foundIt: BOOL, keyDeterminesDict: BOOL, keyStart, keyLen, resultLen: INT, 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: INT; 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 '. before >> 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: TextLooks.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]; [,resultLen] _ Tioga.ReplaceText[world: world, destRoot: keyRoot, sourceRoot: root, dest: [keyNode, keyStart, keyLen], source: [node, 0, textLen]]; IF resultLen # textLen THEN ERROR; IF looks # TextLooks.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.NodeFormat[node] FROM NIL, Tioga.NodeFormat[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 _ EditSpan.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 len0 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] ~ { <> <> 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] ~ { <> <> 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.