DIRECTORY BasicTime USING [GMT, earliestGMT, Now, Period, Update], Convert USING [IntFromRope, RopeFromInt], FS USING [ComponentPositions, ExpandName], IO USING [card, EndOfStream, GetCard, GetInt, GetTokenRope, IDProc, int, PutFR, RIS, rope, STREAM], LoganBerry USING [AttributeType, AttributeValue, Cursor, DeleteEntry, EndGenerate, Entry, Error, GenerateEntries, NextEntry, Open, OpenDB, ReadEntry, WriteEntry], Rope USING [Cat, Concat, Equal, Find, Replace, ROPE, Substr], Thrush USING [EncryptionKey], UserCredentials USING [Get], VoiceRopeDB; VoiceRopeDBImpl: CEDAR PROGRAM -- Should this be a monitor? IMPORTS BasicTime, Convert, FS, IO, Rope, UserCredentials, LoganBerry EXPORTS VoiceRopeDB ~ BEGIN OPEN VoiceRopeDB; ROPE: TYPE ~ Rope.ROPE; STREAM: TYPE ~ IO.STREAM; Error: PUBLIC ERROR [ec: ATOM, explanation: Rope.ROPE _ NIL] = CODE; Open: PUBLIC PROC[dbName: Rope.ROPE] RETURNS [handle: Handle _ NIL] = { ENABLE LoganBerry.Error => ERROR Error[ec, explanation]; vrDB, interestDB: ROPE; cp: FS.ComponentPositions; IF dbName = NIL THEN RETURN[NIL]; [vrDB, cp] _ FS.ExpandName[dbName]; IF cp.ext.length = 0 THEN vrDB _ Rope.Concat[vrDB, ".df"]; interestDB _ Rope.Replace[base: vrDB, start: cp.base.start+cp.base.length-3, len: 0, with: "Interest"]; handle _ NEW[HandleRec _ [voiceRopeDBName: vrDB, voiceInterestDBName: interestDB]]; handle.voiceRopeDB _ LoganBerry.Open[dbName: handle.voiceRopeDBName]; handle.voiceInterestDB _ LoganBerry.Open[dbName: handle.voiceInterestDBName]; }; ReadVoiceRope: PUBLIC PROC [handle: Handle, ropeID: ID] RETURNS [header: Header, struct: TuneList] ~ { ENABLE LoganBerry.Error => ERROR Error[ec, explanation]; IF handle = NIL THEN Error[$BadHandle, "NIL handle"]; header _ LoganBerry.ReadEntry[db: handle.voiceRopeDB, key: $VRID, value: ropeID].entry; struct _ EntryToTuneList[header]; }; WriteVoiceRope: PUBLIC PROC [handle: Handle, vr: VoiceRopeInfo, replace: BOOLEAN _ FALSE] RETURNS [info: VoiceRopeInfo] ~ { ENABLE LoganBerry.Error => ERROR Error[ec, explanation]; entry: LoganBerry.Entry; IF handle = NIL THEN Error[$BadHandle, "NIL handle"]; IF vr.creator = NIL THEN vr.creator _ UserCredentials.Get[].name; IF vr.vrID = NIL THEN vr.vrID _ GenerateUniqueID[vr.creator]; entry _ PackHeader[vr]; LoganBerry.WriteEntry[db: handle.voiceRopeDB, entry: entry, replace: replace ! LoganBerry.Error => IF ec = $ValueNotUnique THEN {vr.vrID _ ReplaceID[entry, vr.creator]; RETRY}]; RETURN[vr]; }; DeleteVoiceRope: PUBLIC PROC [handle: Handle, ropeID: ID] ~ { ENABLE LoganBerry.Error => ERROR Error[ec, explanation]; IF handle = NIL THEN Error[$BadHandle, "NIL handle"]; LoganBerry.DeleteEntry[db: handle.voiceRopeDB, key: $VRID, value: ropeID]; }; VoiceRopeContainingTune: PUBLIC PROC [handle: Handle, tune: INT] RETURNS [header: Header] ~ { ENABLE LoganBerry.Error => ERROR Error[ec, explanation]; IF handle = NIL THEN Error[$BadHandle, "NIL handle"]; header _ LoganBerry.ReadEntry[db: handle.voiceRopeDB, key: $TID, value: Convert.RopeFromInt[tune]].entry; }; EnumerateVoiceRopes: PUBLIC PROC [handle: Handle, start: ID _ NIL, proc: EnumProc] ~ { ENABLE LoganBerry.Error => ERROR Error[ec, explanation]; continue: BOOLEAN _ TRUE; entry: LoganBerry.Entry; info: VoiceRopeInfo; cursor: LoganBerry.Cursor; IF handle = NIL THEN Error[$BadHandle, "NIL handle"]; cursor _ LoganBerry.GenerateEntries[db: handle.voiceRopeDB, key: $VRID, start: start]; entry _ LoganBerry.NextEntry[cursor: cursor]; UNTIL entry = NIL OR NOT continue DO info _ UnpackHeader[entry]; continue _ proc[info]; entry _ LoganBerry.NextEntry[cursor: cursor]; ENDLOOP; LoganBerry.EndGenerate[cursor: cursor]; }; SimpleTuneList: PUBLIC PROC [tune: TuneID, start: INT, length: INT, key: Thrush.EncryptionKey] RETURNS [list: TuneList] ~ { list _ LIST[[$TID, Convert.RopeFromInt[tune]], [$Key, MarshalKey[key]], [$SL, MarshalInterval[start, length]]]; }; NextTuneOnList: PUBLIC PROC [list: TuneList] RETURNS [tune: TuneID, start: INT, length: INT, key: Thrush.EncryptionKey, rest: TuneList] ~ { IF list = NIL THEN Error[$BadTuneList, "NIL tune list"]; tune _ Convert.IntFromRope[list.first.value]; key _ UnmarshalKey[list.rest.first.value]; [start, length] _ UnmarshalInterval[list.rest.rest.first.value]; rest _ list.rest.rest.rest; }; EntryToTuneList: PROC [entry: LoganBerry.Entry] RETURNS [struct: TuneList] ~ { IF entry = NIL THEN RETURN[NIL]; UNTIL entry.first.type = $TID DO entry _ entry.rest; IF entry = NIL THEN Error[$BadTuneList, "No TID in entry"]; ENDLOOP; struct _ entry; }; TuneListInterval: PUBLIC PROC [list: TuneList, start: INT _ 0, length: INT _ -1] RETURNS [new: TuneList] ~ { tuneStart, tuneLength: INT; -- the current tune's interval prevSamples: INT; -- number of samples in entry excluding the current tune interval samples: INT; -- number of samples in entry including the current tune interval intervalMustChange: BOOLEAN _ FALSE; IF list = NIL THEN RETURN[NIL]; IF length > (LAST[INT] - start) THEN -- start + length would cause an integer overflow length _ -1; -- essentially want the whole length prevSamples _ 0; [tuneStart, tuneLength] _ UnmarshalInterval[list.rest.rest.first.value]; IF tuneLength < 0 THEN ERROR Error[$UnspecifiedInterval, "Some tune interval has length=-1"]; samples _ tuneLength; UNTIL samples > start DO prevSamples _ samples; list _ list.rest.rest.rest; IF list = NIL THEN RETURN[NIL]; [tuneStart, tuneLength] _ UnmarshalInterval[list.rest.rest.first.value]; IF tuneLength < 0 THEN ERROR Error[$UnspecifiedInterval, "Some tune interval has length=-1"]; samples _ samples + tuneLength; ENDLOOP; new _ list; IF prevSamples # start THEN { -- don't want first part of existing interval tuneStart _ tuneStart + (start - prevSamples); tuneLength _ tuneLength - (start - prevSamples); intervalMustChange _ TRUE; }; IF (length >= 0) AND (samples >= start + length) THEN { -- interval contained within a single tune interval tuneLength _ length; new.rest.rest.rest _ NIL; -- truncate list since we have has much as we need intervalMustChange _ TRUE; }; IF intervalMustChange THEN new.rest.rest.first.value _ MarshalInterval[tuneStart, tuneLength]; IF (length < 0) OR (samples >= start + length) THEN -- no need to go on RETURN[new]; UNTIL samples >= start + length DO prevSamples _ samples; list _ list.rest.rest.rest; IF list = NIL THEN RETURN[new]; [tuneStart, tuneLength] _ UnmarshalInterval[list.rest.rest.first.value]; IF tuneLength < 0 THEN ERROR Error[$UnspecifiedInterval, "Some tune interval has length=-1"]; samples _ samples + tuneLength; ENDLOOP; IF samples # start + length THEN { -- want only first part of tune interval list.rest.rest.first.value _ MarshalInterval[tuneStart, start + length - prevSamples]; }; list.rest.rest.rest _ NIL; -- truncate list since we have has much as we need RETURN[new]; }; PackHeader: PROC [info: VoiceRopeInfo] RETURNS [header: Header] ~ { entry: LoganBerry.Entry; entry _ info.struct; IF info.editAccess # NIL THEN entry _ CONS[[$EAccess, MarshalList[info.editAccess]], entry]; IF info.playAccess # NIL THEN entry _ CONS[[$PAccess, MarshalList[info.playAccess]], entry]; entry _ CONS[[$Length, Convert.RopeFromInt[info.length]], entry]; entry _ CONS[[$Creator, info.creator], entry]; entry _ CONS[[$VRID, info.vrID], entry]; RETURN[entry]; }; ReplaceID: PROC [header: Header, userName: ROPE] RETURNS [newID: ID] ~ { newID _ BadUniqueID[userName]; header.first.value _ newID; }; UnpackHeader: PUBLIC PROC [header: Header] RETURNS [info: VoiceRopeInfo] ~ { list: LoganBerry.Entry _ header; IF header = NIL THEN RETURN; info.vrID _ list.first.value; list _ list.rest; list _ list.rest; info.length _ Convert.IntFromRope[list.first.value]; list _ list.rest; IF list.first.type = $PAccess THEN { info.playAccess _ UnMarshalList[list.first.value]; list _ list.rest; }; IF list.first.type = $EAccess THEN { info.editAccess _ UnMarshalList[list.first.value]; list _ list.rest; }; [info.creator, info.timestamp] _ ParseUniqueID[info.vrID]; info.struct _ list; }; AddInterest: PUBLIC PROC [handle: Handle, interest: InterestInfo] RETURNS [info: InterestInfo] ~ { ENABLE LoganBerry.Error => ERROR Error[ec, explanation]; entry: LoganBerry.Entry _ NIL; oldEntry: LoganBerry.Entry; IF handle = NIL THEN Error[$BadHandle, "NIL handle"]; IF interest.creator = NIL THEN interest.creator _ UserCredentials.Get[].name; oldEntry _ ReadInterest[handle, interest.vrID, interest.class, interest.refID]; interest.interestID _ IF oldEntry = NIL THEN GenerateUniqueID[interest.creator] ELSE oldEntry.first.value; entry _ PackInterest[interest]; LoganBerry.WriteEntry[db: handle.voiceInterestDB, entry: entry, replace: oldEntry # NIL ! LoganBerry.Error => IF ec = $ValueNotUnique AND oldEntry = NIL THEN {interest.interestID _ ReplaceInterestID[entry, interest.creator]; RETRY}]; RETURN[interest]; }; DropInterest: PUBLIC PROC [handle: Handle, interest: InterestInfo] ~ { ENABLE LoganBerry.Error => ERROR Error[ec, explanation]; entry: LoganBerry.Entry; IF handle = NIL THEN Error[$BadHandle, "NIL handle"]; entry _ ReadInterest[handle, interest.vrID, interest.class, interest.refID]; IF entry = NIL THEN RETURN; LoganBerry.DeleteEntry[db: handle.voiceInterestDB, key: $IID, value: entry.first.value]; }; ReadInterest: PROC [handle: Handle, ropeID: ID _ NIL, class: Rope.ROPE _ NIL, refID: Rope.ROPE] RETURNS [entry: LoganBerry.Entry] ~ { info: InterestInfo; cursor: LoganBerry.Cursor _ LoganBerry.GenerateEntries[db: handle.voiceInterestDB, key: $RefID, start: refID, end: refID]; entry _ LoganBerry.NextEntry[cursor: cursor]; UNTIL entry = NIL DO info _ UnpackInterest[entry]; IF (ropeID = NIL OR Rope.Equal[info.refID, refID]) AND (class = NIL OR Rope.Equal[info.class, class]) THEN EXIT; entry _ LoganBerry.NextEntry[cursor: cursor]; ENDLOOP; LoganBerry.EndGenerate[cursor: cursor]; }; InterestForRef: PUBLIC PROC [handle: Handle, class: Rope.ROPE, refID: Rope.ROPE] RETURNS [interest: Interest] ~ { ENABLE LoganBerry.Error => ERROR Error[ec, explanation]; IF handle = NIL THEN Error[$BadHandle, "NIL handle"]; interest _ ReadInterest[handle: handle, class: class, refID: refID]; }; InterestInVoiceRope: PUBLIC PROC [handle: Handle, ropeID: ID] RETURNS [interest: Interest] ~ { ENABLE LoganBerry.Error => ERROR Error[ec, explanation]; IF handle = NIL THEN Error[$BadHandle, "NIL handle"]; interest _ LoganBerry.ReadEntry[db: handle.voiceInterestDB, key: $VRID, value: ropeID].entry; }; EnumerateInterestClass: PUBLIC PROC [handle: Handle, class: Rope.ROPE, proc: InterestProc] ~ { ENABLE LoganBerry.Error => ERROR Error[ec, explanation]; continue: BOOLEAN _ TRUE; entry: LoganBerry.Entry; info: InterestInfo; cursor: LoganBerry.Cursor; IF handle = NIL THEN Error[$BadHandle, "NIL handle"]; IF proc = NIL THEN Error[$NoProc, "No procedure passed to EnumerateInterestClass"]; cursor _ LoganBerry.GenerateEntries[db: handle.voiceInterestDB, key: $Class, start: class, end: class]; entry _ LoganBerry.NextEntry[cursor: cursor]; UNTIL entry = NIL OR NOT continue DO info _ UnpackInterest[entry]; continue _ proc[info]; entry _ LoganBerry.NextEntry[cursor: cursor]; ENDLOOP; LoganBerry.EndGenerate[cursor: cursor]; }; PackInterest: PUBLIC PROC [info: InterestInfo] RETURNS [interest: Interest] ~ { interest _ NIL; IF info.data # NIL THEN interest _ CONS[[$Data, info.data], interest]; interest _ CONS[[$RefID, info.refID], interest]; interest _ CONS[[$Class, info.class], interest]; interest _ CONS[[$VRID, info.vrID], interest]; interest _ CONS[[$IID, info.interestID], interest]; }; ReplaceInterestID: PROC [interest: Interest, userName: ROPE] RETURNS [newID: ID] ~ { newID _ BadUniqueID[userName]; interest.first.value _ newID; }; UnpackInterest: PUBLIC PROC [interest: Interest] RETURNS [info: InterestInfo] ~ { list: LoganBerry.Entry _ interest; IF interest = NIL THEN RETURN; info.interestID _ list.first.value; list _ list.rest; info.vrID _ list.first.value; list _ list.rest; info.class _ list.first.value; list _ list.rest; info.refID _ list.first.value; list _ list.rest; IF list # NIL THEN info.data _ list.first.value; [info.creator, info.timestamp] _ ParseUniqueID[info.interestID]; }; MarshalKey: PROC [key: Thrush.EncryptionKey] RETURNS [r: ROPE] ~ TRUSTED { cardKey: LONG POINTER TO ARRAY[0..2) OF LONG CARDINAL=LOOPHOLE [LONG[@key]]; r _ IO.PutFR["%bB %bB", IO.card[cardKey[0]], IO.card[cardKey[1]]]; }; UnmarshalKey: PROC [r: ROPE] RETURNS [key: Thrush.EncryptionKey] ~ TRUSTED { cardKey: LONG POINTER TO ARRAY[0..2) OF LONG CARDINAL=LOOPHOLE[LONG[@key]]; keyStream: IO.STREAM _ IO.RIS[r]; cardKey[0] _ IO.GetCard[keyStream]; cardKey[1] _ IO.GetCard[keyStream]; }; MarshalInterval: PROC [start: INT, length: INT] RETURNS [r: ROPE] ~ INLINE { r _ IO.PutFR["%g %g", IO.int[start], IO.int[length]]; }; UnmarshalInterval: PROC [r: ROPE] RETURNS [start: INT, length: INT] ~ INLINE { s: IO.STREAM _ IO.RIS[r]; start _ IO.GetInt[s]; length _ IO.GetInt[s]; }; MarshalList: PROC [l: LIST OF ROPE] RETURNS [r: ROPE] ~ { r _ NIL; WHILE l#NIL DO r _ Rope.Cat[r, l.first, " "]; l _ l.rest; ENDLOOP; }; UnMarshalList: PROC [r: ROPE] RETURNS [l: LIST OF ROPE] ~ { s: IO.STREAM _ IO.RIS[r]; l _ NIL; DO n: ROPE _ IO.GetTokenRope[s, IO.IDProc ! IO.EndOfStream => EXIT].token; l _ CONS[n, l]; ENDLOOP; }; counter: INT; kicks: INT; -- for instrumentation purposes GenerateUniqueID: PROC [userName: ROPE] RETURNS [id: ROPE] ~ { timestamp: INT _ MAX[counter, BasicTime.Period[BasicTime.earliestGMT, BasicTime.Now[]]]; id _ IO.PutFR["%g#%g", IO.rope[userName], IO.int[timestamp]]; counter _ counter + 1; }; BadUniqueID: PROC [userName: ROPE] RETURNS [id: ROPE] ~ { counter _ BasicTime.Period[BasicTime.earliestGMT, BasicTime.Now[]]+1; -- kick the counter kicks _ kicks + 1; id _ GenerateUniqueID[userName]; }; ParseUniqueID: PROC [id: ROPE] RETURNS [creator: ROPE, timestamp: BasicTime.GMT] ~ { seconds: INT; i: INT _ Rope.Find[s1: id, s2: "#"]; creator _ Rope.Substr[base: id, start: 0, len: i]; seconds _ Convert.IntFromRope[Rope.Substr[base: id, start: i+1]]; timestamp _ BasicTime.Update[BasicTime.earliestGMT, seconds]; }; InitUniqueID: PROC [] RETURNS [] ~ { counter _ BasicTime.Period[BasicTime.earliestGMT, BasicTime.Now[]]; kicks _ 0; }; InitUniqueID[]; END. hVoiceRopeDBImpl.mesa Copyright ำ 1986, 1987 by Xerox Corporation. All rights reserved. Doug Terry, July 28, 1987 10:29:50 pm PDT Swinehart, April 9, 1987 2:43:20 pm PDT Routines for storing voice ropes in a database and manipulating their structure. It is unresolved where a tune's encryption key should be stored. For now, it is kept with the tune interval; though this is the wrong place. Perhaps, it should be kept in the tune's header. VoiceRope database operations Voice ropes are stored in a LoganBerry database as a header followed by a list of tune intervals (a TuneList). Interests in voice ropes are also maintained in a LoganBerry database. Prepares the given database for service. The name of the voice rope database is presumed to be sLB.DF. The name of the interest database is derived from it, producing InterestsLB.DF. Returns the structure of the given voice rope. Writes the voice rope information into the LoganBerry database. Deletes the given voice rope (regardless of what interests may exist). Returns a voice rope containing the given tune, if one exists. Calls the enumeration procedure for every voice rope with id greater than start; start=NIL represents the first element of the database. Stops when proc returns FALSE or the end of the database is encountered. Voice rope structure WARNING: For efficiency reasons, much of this code relies on the exact structure of entries stored in a LoganBerry database. For instance, it assumes that the first attribute of an entry is a voice rope (or interest) ID and does not check the attribute type to verify this. Do NOT reorder attributes unless care is taken to change the code dependencies. A tune list, the structure of a voice rope, is represented in a LoganBerry database as an ordered list of $TID (the tune's ID), $Key (the tune's encryption key), and $SL (the start and length of a tune interval) attributes. The tune's encryption key should probably be kept in the tune's header. Builds a tune list with a single tune's information. Returns information about the next tune on the list; assumes that list really points to a properly structured TuneList so doesn't bother to check attribute types. The rest of the list is returned so this routine can be repetitively called to get all the tunes on the list. Returns the tune list representing the structure of the voice rope. Returns the tune list representing the structure of the voice rope interval. If the list contains intervals of unspecified length (-1) then the desired voice rope interval may not be determinable and an Error[$UnspecifiedInterval] is raised. Warning: this operation modifies the original list! at this point: prevSamples <= start < samples at this point: prevSamples <= start + length <= samples the creator field is not really needed, but is included for compatibility with existing databases Returns the information associated with a voice rope header. ignore creator field Interests database Adds an entry to the interest database if a similar entry does not already exist. Removes an entry from the interest database. Searches for an entry with matching ropeID, class, and refID. Returns an interest with the given class and refID, if one exists. Returns an interest for the voice rope, if one exists. Calls the enumeration procedure for every interest of the given class. Stops when proc returns FALSE or the end of the database is encountered. Conversions (marshalling) Writes a start and length field into a rope. Parses the input rope into a start and length field. Writes a list of names into a rope. Parses the input rope into a list of names. Generating unique identifiers A unique identifier is generated by concatenating a user's Rname with a timestamp. The timestamp is taken to be the maximum of the current time (converted to an integer) and a simple counter. The current time alone is not sufficient since it has a granularity of seconds. Several voice ropes may be created within a second, but the long-term creation rate should be much less than one per second. The counter is initialized to the current time, which could cause some problems if this module is rerun before the current time has a chance to catch up to the old counter (or if a machine's clock is reset to an earlier time). This problem is detected by $ValueNotUnique errors from LoganBerry when one attempts to write a new voice rope. Note that the counter, as maintain by these routines, is sufficient as a unique ID if this code is run on the voice server. However, having a user's name and current timestamp as part of the permanent voice rope ID provides information that might be useful. This routine should be called if some generated ID does not turn out to be unique. It trys once again to generate a unique ID after advancing the counter. Initializations Doug Terry, June 3, 1986 4:19:14 pm PDT Extracted database operations from VoiceRopeImpl.mesa. changes to: VoiceRopeDBImpl, EXPORTS, ~, WriteVoiceRope Doug Terry, June 3, 1986 5:23:23 pm PDT changes to: VoiceRopeDBImpl, EXPORTS, ~, Open, WriteVoiceRope, END, DIRECTORY, IMPORTS, Read, SimpleTuneList, NextTuneOnList, TuneListInterval, EntryToTuneList Doug Terry, June 5, 1986 11:47:40 am PDT changes to: Open, Read, Write, EntryToTuneList, Error, DIRECTORY Doug Terry, June 5, 1986 3:07:30 pm PDT changes to: EntryToTuneList, TuneListInterval Doug Terry, June 10, 1986 5:55:22 pm PDT changes to: AddInterest, DropInterest, LookupInterest, Write, Delete, ~, LookupVoiceRope, LookupTune Doug Terry, June 11, 1986 3:40:47 pm PDT changes to: LookupTune, Enumerate, LookupVoiceRope, EnumerateInterests, ParseInterestEntry, AddInterest, DropInterest, LookupInterest, CRQuery, RCRQuery Doug Terry, June 12, 1986 4:45:50 pm PDT changes to: Open, ReadVoiceRope, EntryToTuneList, ~, WriteVoiceRope, PackHeader, ReplaceID, UnpackHeader, PackInterest, UnpackInterest, DeleteVoiceRope, VoiceRopeContainingTune, EnumerateVoiceRopes, AddInterest, DropInterest, ReplaceInterestID, ReadInterest, InterestForRef, InterestInVoiceRope, EnumerateInterestClass, BadUniqueID, ParseUniqueID, DIRECTORY Doug Terry, June 17, 1986 12:02:00 pm PDT changes to: UnpackHeader Doug Terry, June 26, 1986 2:08:18 pm PDT changes to: TuneListInterval Doug Terry, June 26, 1986 5:03:38 pm PDT changes to: Open, DIRECTORY, IMPORTS Doug Terry, June 30, 1986 1:11:17 pm PDT changes to: PackInterest Doug Terry, June 30, 1986 2:36:30 pm PDT changes to: UnpackHeader, Open, ReadVoiceRope, WriteVoiceRope, DeleteVoiceRope, VoiceRopeContainingTune, EnumerateVoiceRopes, AddInterest, DropInterest, InterestForRef, InterestInVoiceRope, EnumerateInterestClass, NextTuneOnList, EntryToTuneList, UnpackInterest Doug Terry, August 18, 1986 4:53:00 pm PDT Changes for server-side operation: now uses LoganBerry directly rather than LoganBerryStub. changes to: DIRECTORY, IMPORTS, WriteVoiceRope, SimpleTuneList, NextTuneOnList, TuneListInterval, MarshalInterval, UnmarshalInterval Doug Terry, October 8, 1986 11:21:42 am PDT Changed back to using LoganBerryStub. changes to: IMPORTS  Doug Terry, October 8, 1986 11:22:30 am PDT changes to: DIRECTORY, IMPORTS Swinehart, April 8, 1987 9:11:16 am PDT Cedar 7. LoganBerryStub => LoganBerry changes to: DIRECTORY, IMPORTS ส˜codešœ™KšœB™BKšœ)™)K™'—K™K™PK™K™ภK™šฯk ˜ Kšœ œ)˜8Kšœœ˜)Kšœœ"˜*KšœœHœœ˜cKšœ œ’˜ขKš œœ œ œœ˜=Jšœœ˜Kšœœ˜Kšœ ˜ —K˜Kšะblœœœฯc˜˜EKšœ ˜šœ˜Kšœ ˜K˜Kšœœœ˜Kšœœœœ˜—head™Jšœถ™ถK™Icode0š œœœœœœœ˜DJ™š ฯnœ œœœœ˜GJ™(J™@J™RJšœœ˜8Jšœœ˜Jšœ˜J•StartOfExpansion"[name: ROPE, wDir: ROPE _ NIL]š œ œœœœ˜!Jšœ#˜#šœ˜Jšœ ˜ —–M[base: ROPE, start: INT _ 0, len: INT _ 2147483647, with: ROPE _ NIL]šœ ˜ JšœZ˜Z—Jšœ œG˜SJšœE˜EJšœM˜MJ˜—K˜š   œœœœœ'˜fK™.Jšœœ˜8Kšœ œœ!˜5KšœW˜WKšœ!˜!K˜K™—š  œ œ.œœœ˜{K™?Jšœœ˜8K˜Kšœ œœ!˜5šœœ˜Kšœ(˜(—šœ œ˜Kšœ'˜'—Kšœ˜Kšœcœœ*œ˜ฑKšœ˜ K˜K˜—š œ œ!˜=K™FJšœœ˜8Kšœ œœ!˜5KšœJ˜JK˜K˜—š œ œœœ˜]K™>Jšœœ˜8Kšœ œœ!˜5Kšœj˜jK˜K˜—š œ œœœ˜VK™าJšœœ˜8K–ซ[conv: LoganBerry.Conv _ NIL, db: LoganBerry.OpenDB, key: LoganBerry.AttributeType, start: LoganBerry.AttributeValue _ NIL, end: LoganBerry.AttributeValue _ NIL]šœ œœ˜Kšœ˜Kšœ˜Kšœ˜Kšœ œœ!˜5KšœV˜VK–l[conv: LoganBerry.Conv _ NIL, cursor: LoganBerry.Cursor, dir: LoganBerry.CursorDirection _ increasing]šœ-˜-š œ œœœ ˜$Kšœ˜Kšœ˜K–l[conv: LoganBerry.Conv _ NIL, cursor: LoganBerry.Cursor, dir: LoganBerry.CursorDirection _ increasing]šœ-˜-Kšœ˜—Kšœ'˜'K˜——™Kšœไ™ไK™Jšœจ™จK˜š  œ œœ œœ˜{K™4Kšœœd˜oK˜K˜—š  œ œœœ œ0˜‹K™‘Kšœœœ&˜8Kšœ-˜-Kšœ*˜*Kšœ@˜@K˜K˜K˜—š œœœ˜NK™CKš œ œœœœ˜ šœœ˜!Kšœ˜Kšœ œœ(˜;Kšœ˜—Kšœ˜K˜K™—š  œ œœœœ˜lKšœฆ™ฆKšœœŸ˜;Kšœ œŸA˜TKšœ œŸA˜PKšœœœ˜$Kš œœœœœ˜š œ œœ œŸ1˜WKšœŸ$˜2—Kšœ˜KšœH˜Hšœœ˜KšœA˜F—Kšœ˜šœ˜Kšœ˜Kšœ˜Kš œœœœœ˜KšœH˜Hšœœ˜KšœA˜F—Kšœ˜Kšœ˜—Kšœ-™-Kšœ ˜ šœœŸ-˜LKšœ.˜.Kšœ0˜0Kšœœ˜K˜—šœœœŸ3˜kKšœ˜KšœœŸ2˜MKšœœ˜K˜—šœœ˜KšœC˜C—šœœœŸ˜GKšœ˜ —šœ˜"Kšœ˜Kšœ˜Kšœœœœ˜KšœH˜Hšœœ˜KšœA˜F—Kšœ˜Kšœ˜—Kšœ7™7šœœŸ(˜LKšœV˜VK˜—KšœœŸ2˜NKšœ˜ K˜—K˜š  œœœ˜CK˜Kšœ˜šœœ˜Kšœœ2˜>—šœœ˜Kšœœ2˜>—Kšœœ5˜AK™aKšœœ#˜/Kšœœ˜(Kšœ˜K˜—K˜š   œœœœ œ˜HKšœ˜Kšœ˜K˜—K˜š  œœœœ˜LJ™>Kšœ ˜ Kšœ œœœ˜Kšœ˜Kšœ˜Kšœ™Kšœ˜Kšœ4˜4Kšœ˜šœœ˜$Kšœ2˜2Kšœ˜K˜—šœœ˜$Kšœ2˜2Kšœ˜K˜—Kšœ:˜:Kšœ˜K˜——™š  œ œ*œ˜bK™QJšœœ˜8Kšœœ˜Kšœ˜Kšœ œœ!˜5šœœ˜Kšœ.˜.—KšœO˜OKš œœ œœ#œ˜jKšœ˜Kš œTœœœ œœDœ˜้Kšœ ˜K˜K˜—š  œ œ-˜FK™,Jšœœ˜8Kšœ˜Kšœ œœ!˜5KšœL˜LKšœ œœœ˜KšœX˜XK˜K˜—š   œœœ œœœ˜…Kšœ=™=Kšœ˜Kšœz˜zK–l[conv: LoganBerry.Conv _ NIL, cursor: LoganBerry.Cursor, dir: LoganBerry.CursorDirection _ increasing]šœ-˜-šœ œ˜Kšœ˜Kšœ œœ œ œœ œœ˜pK–l[conv: LoganBerry.Conv _ NIL, cursor: LoganBerry.Cursor, dir: LoganBerry.CursorDirection _ increasing]šœ-˜-Kšœ˜—Kšœ'˜'K˜K˜—š  œ œœœœ˜qK™BJšœœ˜8Kšœ œœ!˜5KšœD˜DK˜K˜—š œ œœœ˜^K™6Jšœœ˜8Kšœ œœ!˜5Kšœ^˜^K˜K˜—š œ œœ˜^K™Jšœœ˜8K–ซ[conv: LoganBerry.Conv _ NIL, db: LoganBerry.OpenDB, key: LoganBerry.AttributeType, start: LoganBerry.AttributeValue _ NIL, end: LoganBerry.AttributeValue _ NIL]šœ œœ˜Kšœ˜Kšœ˜Kšœ˜Kšœ œœ!˜5KšœœœA˜SKšœg˜gK–l[conv: LoganBerry.Conv _ NIL, cursor: LoganBerry.Cursor, dir: LoganBerry.CursorDirection _ increasing]šœ-˜-š œ œœœ ˜$Kšœ˜Kšœ˜K–l[conv: LoganBerry.Conv _ NIL, cursor: LoganBerry.Cursor, dir: LoganBerry.CursorDirection _ increasing]šœ-˜-Kšœ˜—Kšœ'˜'K˜K˜—š  œ œœ˜OKšœ œ˜šœ œ˜Kšœ œ˜.—Kšœ œ!˜0Kšœ œ!˜0Kšœ œ˜.Kšœ œ$˜3K˜—K˜š  œœ œœ œ˜TKšœ˜Kšœ˜K˜K˜—š œ œœ˜QKšœ"˜"Kšœ œœœ˜Kšœ#˜#Kšœ˜Kšœ˜Kšœ˜Kšœ˜Kšœ˜Kšœ˜Kšœ˜šœœ˜Kšœ˜—Kšœ@˜@K˜K˜——™š   œœœœœ˜JJšœ œœœœœœœœœ˜LJšœœœœ˜BK˜K˜—š   œœœœœ˜LJšœ œœœœœœœœœ˜KJš œ œœœœ˜!Jšœ œ˜#Jšœ œ˜#K˜K˜—š œœ œ œœœœ˜LK™,Kšœœœ œ˜5K˜K™—š œœœœ œ œœ˜NK™4Kš œœœœœ˜Kšœœ ˜Kšœ œ ˜K˜K™—š  œœœœœœœ˜9K™#Kšœœ˜šœœ˜K˜K˜ Kšœ˜—Kšœ˜K˜—š  œœœœœœœ˜;K™+Kš œœœœœ˜Kšœœ˜š˜Kš œœœœ œœ˜GKšœœ˜Kšœ˜—K˜K˜——™Kšœไ™ไK™Kšœ‚™‚K˜Kšœ œ˜ KšœœŸ˜,K˜š  œœ œœœ˜>Kšœ œœD˜XKšœ=˜=K˜K˜—K˜š   œœ œœœ˜9K™›KšœGŸ˜ZK˜Kšœ ˜ K˜—K˜š   œœœœ œœ˜TK–>[s1: ROPE, s2: ROPE, pos1: INT _ 0, case: BOOL _ TRUE]šœ œ˜ Kšœœ˜$Kšœ2˜2KšœA˜AKšœ=˜=K˜—K˜š  œœœ˜$KšœC˜CK˜ K˜——™Kšœ˜K™—K˜Kšœ˜K™™'K™6Kšœ ฯr+™7—™'Kšœ ก“™Ÿ—™(Kšœ ก4™@—™'Kšœ ก!™-—™(Kšœ กX™d—™(Kšœ กŒ™˜—™(Kšœ กู™ๅ—™)Kšœ ก ™—™(Kšœ ก™—™(Kšœ ก™$—™(Kšœ ก ™—™(Kšœ ก๙™…—™*K™[Kšœ กx™„—™+K™%Kšœ กœก™—™+Kšœ ก™—™'K™&Kšœ ก™—K™—…—7ji่