DIRECTORY BTree USING [Compare, CompareEntries, Comparison, Entry, EntrySize, Key, NewRecordFromEntry, PageNumber, PagePtr, PageSize, PageStorage, PageStoragePrimitives, Reason, Record, ReferencePage, ReferenceType, Relation, ReleasePage, RepresentationPrimitives, State, statePage, UpdateState], BTreeInternal USING [AssignRefESR, BackUpOneEntry, BTreeEntrySize, BTreePagePtr, BugType, Compare, CompareEntries, entry0Offset, entry1Offset, EntryOrdinal, entryOverhead, EntrySize, EntryTable, GetDefaultPathStk, Heap, Lock, LockMode, NewRecordFromEntry, nilOffset, nilPage, OffsetTable, OffsetTableObject, PageOffset, pageOverhead, PathEntryLE, PathStk, PathStkEntry, PathStkIndex, PathStkObject, PathToMaxDescendant, ReferencePage, ReferenceStack, ReleasePage, RepairOffsets, reservedWordsPerPage, ReturnDefaultPathStk, sealValue, Tree, TreeObject, TreeState, Unlock, WriteStatePage], File USING [Error, wordsPerPage], PrincOpsUtils USING [BITSHIFT, LongCopy, LongZero]; BTreeRead: MONITOR LOCKS tree USING tree: Tree IMPORTS BTreeInternal, File, PrincOpsUtils EXPORTS BTree, BTreeInternal = { OPEN BTree, BTreeInternal; CARD: TYPE = LONG CARDINAL; Tree: TYPE = REF TreeObject; TreeObject: PUBLIC TYPE = BTreeInternal.TreeObject; PathStk: TYPE = REF PathStkObject; PathStkObject: PUBLIC TYPE = BTreeInternal.PathStkObject; New: PUBLIC SAFE PROC [repPrim: RepresentationPrimitives, storPrim: PageStoragePrimitives, minEntrySize: CARDINAL _ 1, initialState: State[closed..suspended] _ closed] RETURNS [tree: Tree] = CHECKED { tree _ NEW [TreeObject _ [state: [], repPrim: repPrim, storPrim: storPrim, minEntrySize: minEntrySize, objectState: initialState]]; }; Open: PUBLIC SAFE PROC [tree: Tree, storage: PageStorage, pageSize: PageSize _ File.wordsPerPage, initialize: BOOL _ FALSE, maintainRecomputableState: BOOL _ TRUE] = TRUSTED { DO tree.Lock[update, FALSE]; IF tree.objectState#open THEN EXIT; tree.Unlock[]; SetState[tree, closed]; ENDLOOP; tree.state _ [pageSize: pageSize]; tree.maxFreeWords _ pageSize-pageOverhead; tree.maxRecordsPerPage _ tree.maxFreeWords/(tree.minEntrySize+entryOverhead); tree.awfullyFull _ (9*tree.maxFreeWords)/10; tree.prettyFull _ (2*tree.maxFreeWords)/3; tree.fairlyFull _ tree.maxFreeWords/2; tree.breathingSpace _ tree.maxFreeWords-tree.awfullyFull; tree.storage _ storage; tree.maintainRecomputableState _ maintainRecomputableState; tree.id _ (treeID _ treeID+1); -- this doesn't assign a reliably unique ID, since treeID is not protected by any monitor; but it doesn't really matter since the ID is merely protecting against client blunders anyway. { ENABLE UNWIND => tree.Unlock[]; IF initialize THEN { statePtr: LONG POINTER TO TreeState _ tree.ReferencePage[statePage, new]; PrincOpsUtils.LongZero[statePtr, pageSize]; IF ~maintainRecomputableState THEN tree.state.entryCount _ LAST[CARD]; statePtr^ _ tree.state; tree.ReleasePage[statePage, endOfUpdate]; } ELSE { statePtr: LONG POINTER TO TreeState _ tree.ReferencePage[statePage, read]; IF statePtr.seal#sealValue THEN ERROR Error[badSeal]; IF statePtr.pageSize#pageSize THEN ERROR Error[wrongPageSize]; tree.state _ statePtr^; tree.ReleasePage[statePage]; IF tree.state.updateInProgress THEN SIGNAL UpdateInProgress; }; tree.state.updateInProgress _ FALSE; }; tree.objectState _ open; tree.Unlock[]; }; SetState: PUBLIC SAFE PROC [tree: Tree, state: State[closed..suspended]] = TRUSTED { DO WaitUnlocked: ENTRY PROC [tree: Tree] = INLINE { IF tree.longUpdate THEN WAIT tree.unlocked}; tree.Lock[update, FALSE]; IF tree.objectState#open OR ~tree.longUpdate THEN EXIT; tree.Unlock[]; WaitUnlocked[tree]; ENDLOOP; tree.objectState _ state; tree.storage _ NIL; tree.offsetTable _ NIL; tree.defaultPathStk _ NIL; tree.entryTable _ NIL; tree.heap _ NIL; tree.Unlock[]; }; GetState: PUBLIC ENTRY SAFE PROC [tree: Tree] RETURNS [state: State, entryCount: CARD, greatestPage: PageNumber, depth: CARDINAL, storage: PageStorage] = CHECKED { RETURN [state: tree.objectState, entryCount: tree.state.entryCount, greatestPage: tree.state.greatestPage, depth: tree.state.depth, storage: tree.storage]; }; Validate: PUBLIC SAFE PROC [tree: Tree] = TRUSTED { CheckPair: PROC [entry: Entry] RETURNS [continue: BOOL] = { size: CARDINAL = tree.EntrySize[entry]; IF size+entryOverhead > tree.maxFreeWords THEN ERROR Error[entrySizesWrong]; entryCount _ entryCount+1; IF firstEntry THEN firstEntry _ FALSE ELSE IF tree.CompareEntries[prevEntry, entry]#less THEN ERROR Error[entriesOutOfOrder]; PrincOpsUtils.LongCopy[to: prevEntry, from: entry, nwords: size]; RETURN [TRUE]; }; PrevRecord: TYPE = RECORD [entry: SEQUENCE maxLength: [0..LAST[PageSize]] OF WORD]; prevRecord: REF PrevRecord = NEW[PrevRecord[tree.state.pageSize-reservedWordsPerPage]]; prevEntry: Entry = BASE[DESCRIPTOR[prevRecord.entry]]; entryCount: CARD _ 0; firstEntry: BOOL _ TRUE; [] _ EnumerateEntries[tree: tree, key: NIL, Proc: CheckPair]; IF tree.maintainRecomputableState AND entryCount#tree.state.entryCount THEN { tree.Lock[update]; tree.state.entryCount _ entryCount; tree.WriteStatePage[endOfUpdate]; tree.Unlock[]; }; }; ReadRecord: PUBLIC SAFE PROC [tree: Tree, key: Key, relation: Relation _ equal, pathStk: PathStk _ NIL, useExistingPath: BOOL _ FALSE] RETURNS [record: Record] = TRUSTED { EntryProc: PROC [entry: Entry] = { record _ IF entry#NIL THEN tree.NewRecordFromEntry[entry] ELSE NIL; }; ReadEntry[tree: tree, key: key, relation: relation, pathStk: pathStk, useExistingPath: useExistingPath, Proc: EntryProc]; }; EnumerateRecords: PUBLIC SAFE PROC [tree: Tree, key: Key, relation: Relation _ equal, pathStk: PathStk _ NIL, useExistingPath: BOOL _ FALSE, Proc: PROC [record: Record] RETURNS [continue: BOOL]] RETURNS [exhausted: BOOL] = TRUSTED { EntryProc: PROC [entry: Entry] RETURNS [continue: BOOL] = { RETURN [Proc[tree.NewRecordFromEntry[entry]]]; }; exhausted _ EnumerateEntries[tree: tree, key: key, pathStk: pathStk, useExistingPath: useExistingPath, Proc: EntryProc]; }; ReadEntry: PUBLIC PROC [tree: Tree, key: Key, relation: Relation _ equal, pathStk: PathStk _ NIL, useExistingPath: BOOL _ FALSE, Proc: UNSAFE PROC [entry: Entry]] = { SELECT relation FROM less, lessEqual, equal => { pathStkWasNil: BOOL _ pathStk=NIL; tree.Lock[read]; IF pathStkWasNil THEN { IF useExistingPath THEN ERROR Error[nilPathStk]; pathStk _ tree.GetDefaultPathStk[]; }; { ENABLE UNWIND => { IF pathStkWasNil THEN tree.ReturnDefaultPathStk[pathStk]; tree.Unlock[]; }; equal: BOOL _ tree.PathEntryLE[key: key, pathStk: pathStk, useExistingPath: useExistingPath].equal; IF equal AND relation=less THEN [] _ PathToPreviousEntry[tree, pathStk]; IF pathStk.top=0 OR (~equal AND relation=equal) THEN Proc[NIL] ELSE { pagePtr: BTreePagePtr; pse: LONG POINTER TO PathStkEntry; [ptr: pagePtr, pse: pse] _ tree.ReferenceStack[pathStk]; Proc[@pagePtr[pse.lastOffset].entry ! UNWIND => tree.ReleasePage[pse.pageNumber]]; tree.ReleasePage[pse.pageNumber]; }; }; IF pathStkWasNil THEN tree.ReturnDefaultPathStk[pathStk]; tree.Unlock[]; }; greaterEqual, greater => { CallWhenRelationSatisfied: PROC [entry: Entry] RETURNS [continue: BOOL] = { IF key=NIL OR tree.Compare[key, entry] <= maxComparison THEN {Proc[entry]; RETURN [FALSE]} ELSE RETURN [TRUE]; }; maxComparison: Comparison _ IF relation=greaterEqual THEN equal ELSE less; exhausted: BOOL _ EnumerateEntries[tree: tree, key: key, pathStk: pathStk, useExistingPath: useExistingPath, Proc: CallWhenRelationSatisfied]; IF exhausted THEN Proc[NIL]; }; ENDCASE => ERROR; }; EnumerateEntries: PUBLIC PROC [tree: Tree, key: Key, relation: Relation _ equal, pathStk: PathStk _ NIL, useExistingPath: BOOL _ FALSE, Proc: UNSAFE PROC [entry: Entry] RETURNS [continue: BOOL]] RETURNS [exhausted: BOOL] = { pathStkWasNil: BOOL _ pathStk = NIL; tree.Lock[read]; IF pathStkWasNil THEN { IF useExistingPath THEN ERROR Error[nilPathStk]; pathStk _ tree.GetDefaultPathStk[]; }; { ENABLE UNWIND => { IF pathStkWasNil THEN tree.ReturnDefaultPathStk[pathStk]; tree.Unlock[]; }; leafStkTop: PathStkIndex; pse: LONG POINTER TO PathStkEntry; pagePtr: BTreePagePtr _ NIL; skipFirstEntry: BOOL _ FALSE; equal: BOOL; [equal: equal, depth: leafStkTop] _ tree.PathEntryLE[key: key, pathStk: pathStk, useExistingPath: useExistingPath]; IF pathStk.top=0 THEN { IF leafStkTop=0 THEN GOTO exhausted; -- empty tree pathStk.top _ leafStkTop; pathStk.path[leafStkTop].lastOffset _ entry1Offset; } ELSE SELECT relation FROM less => IF equal AND ~PathToPreviousEntry[tree, pathStk] THEN { pathStk.top _ leafStkTop; pathStk.path[leafStkTop].lastOffset _ entry1Offset; }; lessEqual => NULL; equal, greaterEqual => skipFirstEntry _ ~equal; greater => skipFirstEntry _ TRUE; ENDCASE; exhausted _ FALSE; pse _ @pathStk.path[pathStk.top]; DO offset: PageOffset _ pse.lastOffset; endOffset: PageOffset; IF pagePtr=NIL THEN { pagePtr _ tree.ReferencePage[pse.pageNumber]; endOffset _ nilOffset+(tree.state.pageSize-pagePtr.freeWords); }; IF offset>=endOffset THEN { tree.ReleasePage[pse.pageNumber]; IF offset>endOffset THEN ERROR Error[entrySizesWrong]; pagePtr _ NIL; IF (pathStk.top _ pathStk.top-1) = 0 THEN { pathStk.version _ LAST[CARD]; GOTO exhausted; }; pse _ pse - SIZE[PathStkEntry]; } ELSE { nxtPgNo: PageNumber _ pagePtr[offset].grPage; pse.offset _ offset + tree.BTreeEntrySize[@pagePtr[offset]]; IF skipFirstEntry THEN skipFirstEntry _ FALSE ELSE IF ~Proc[@pagePtr[offset].entry ! UNWIND => tree.ReleasePage[pse.pageNumber]] THEN EXIT; WHILE nxtPgNo#nilPage DO tree.ReleasePage[pse.pageNumber]; pathStk.top _ pathStk.top+1; pse _ pse + SIZE[PathStkEntry]; pagePtr _ tree.ReferencePage[nxtPgNo]; pse.pageNumber _ nxtPgNo; pse.nextToLastOffset _ nilOffset; pse.lastOffset _ entry0Offset; pse.offset _ entry1Offset; endOffset _ nilOffset+(tree.state.pageSize-pagePtr.freeWords); nxtPgNo _ pagePtr.minPage; ENDLOOP; }; pse.nextToLastOffset _ pse.lastOffset; pse.lastOffset _ pse.offset; ENDLOOP; IF pagePtr#NIL THEN tree.ReleasePage[pse.pageNumber]; EXITS exhausted => exhausted _ TRUE; }; IF pathStkWasNil THEN tree.ReturnDefaultPathStk[pathStk]; tree.Unlock[]; }; SalvageEntries: PUBLIC PROC [tree: Tree, Proc: UNSAFE PROC [entry: Entry] RETURNS [continue: BOOL]] RETURNS [exhausted: BOOL] = { tree.Lock[read]; FOR page: PageNumber IN [statePage+1 .. tree.state.greatestPage] DO ENABLE UNWIND => tree.Unlock[]; pagePtr: BTreePagePtr; pagePtr _ tree.ReferencePage[page ! File.Error => LOOP]; IF pagePtr.freeWords IN [0 .. CARDINAL[tree.state.pageSize-pageOverhead]] THEN { offset: PageOffset _ entry1Offset; endOffset: PageOffset = nilOffset+(tree.state.pageSize-pagePtr.freeWords); WHILE offsetendOffset THEN EXIT; IF ~Proc[@pagePtr[offset].entry] THEN GOTO aborted; offset _ offset+entSize; ENDLOOP; }; tree.ReleasePage[page]; REPEAT aborted => exhausted _ FALSE; FINISHED => exhausted _ TRUE; ENDLOOP; tree.Unlock[]; }; NewPathStk: PUBLIC SAFE PROC RETURNS [pathStk: PathStk] = CHECKED { pathStk _ NEW[PathStkObject]; pathStk.version _ LAST[CARD]; -- not valid }; PathEntryLE: PUBLIC PROC [tree: Tree, key: Key, pathStk: PathStk, useExistingPath: BOOL _ FALSE] RETURNS [equal: BOOL, depth: PathStkIndex] = { BinSearchPage: PROC RETURNS [firstG: EntryOrdinal] = { firstBadOffset: PageOffset = nilOffset + (tree.state.pageSize - pagePtr.freeWords); midOffset: PageOffset _ LOOPHOLE[PrincOpsUtils.BITSHIFT[LOOPHOLE[firstBadOffset], -1]]; curOffset: PageOffset _ entry1Offset; l, r: EntryOrdinal _ 2; IF key#NIL THEN DO IF curOffset>=midOffset THEN { IF curOffset>=firstBadOffset OR tree.Compare[key, @pagePtr[curOffset].entry] = less THEN EXIT; midOffset _ firstBadOffset; l _ r; }; curOffset _ curOffset + tree.BTreeEntrySize[@pagePtr[curOffset]]; r _ r+1; offsetTable[r] _ curOffset; ENDLOOP; IF curOffset>firstBadOffset THEN ERROR Error[entrySizesWrong]; WHILE l r _ m; equal => {equal _ TRUE; RETURN [m+1]}; greater => l _ m+1; ENDCASE; ENDLOOP; RETURN [r]; }; pse: LONG POINTER TO PathStkEntry; offsetTable: OffsetTable; curPage: PageNumber; pagePtr: BTreePagePtr; equal _ FALSE; IF useExistingPath AND pathStk.treeID=tree.id AND pathStk.version=tree.version AND key#NIL THEN { depth _ pathStk.top; IF depth=0 THEN GOTO dontUse; -- who knows whether this is the min key or not? WHILE pathStk.path[depth].leastSon # nilPage DO depth _ depth+1; ENDLOOP; [pse: pse, ptr: pagePtr] _ tree.ReferenceStack[pathStk]; SELECT tree.Compare[key, @pagePtr[pse.lastOffset].entry] FROM less => useExistingPath _ FALSE; -- wrong key equal => equal _ TRUE; -- correct key greater => { IF depth=pathStk.top AND pse.offset < nilOffset+(tree.state.pageSize-pagePtr.freeWords) AND tree.Compare[key, @pagePtr[pse.offset].entry] = less THEN NULL -- key falls between this entry and next; ok ELSE useExistingPath _ FALSE; -- wrong key, or too hard to decide }; ENDCASE; tree.ReleasePage[pse.pageNumber]; IF useExistingPath THEN RETURN; EXITS dontUse => NULL; }; pathStk.top _ 0; pathStk.path[0] _ []; offsetTable _ GetOffsetTable[tree]; offsetTable[0] _ nilOffset; offsetTable[1] _ entry0Offset; offsetTable[2] _ entry1Offset; depth _ 0; pse _ @pathStk.path[0]; curPage _ tree.state.rootPage; WHILE curPage#nilPage DO firstG: EntryOrdinal; pagePtr _ tree.ReferencePage[curPage]; depth _ depth+1; pse _ pse+SIZE[PathStkEntry]; pse.pageNumber _ curPage; firstG _ BinSearchPage[ ! UNWIND => {tree.ReleasePage[curPage]; ReturnOffsetTable[tree, offsetTable]}]; pse.offset _ offsetTable[firstG]; pse.lastOffset _ offsetTable[firstG-1]; pse.nextToLastOffset _ offsetTable[firstG-2]; pse.leastSon _ pagePtr.minPage; IF firstG>2 THEN pathStk.top _ depth; -- real entry in this page AssignRefESR[@pse.eslFront, NIL]; AssignRefESR[@pse.eslRear, NIL]; curPage _ pagePtr[pse.lastOffset].grPage; tree.ReleasePage[pse.pageNumber]; ENDLOOP; pathStk.treeID _ tree.id; pathStk.version _ tree.version; ReturnOffsetTable[tree, offsetTable]; }; ReferenceStack: PUBLIC PROC [tree: Tree, pathStk: PathStk, type: ReferenceType _ read] RETURNS [pse: LONG POINTER TO PathStkEntry, ptr: PagePtr] = { pse _ @pathStk.path[pathStk.top]; ptr _ tree.ReferencePage[pse.pageNumber, type]; }; BackUpOneEntry: PUBLIC PROC [tree: Tree, pse: LONG POINTER TO PathStkEntry] = { tree.RepairOffsets[pse]; pse.offset _ pse.lastOffset; tree.RepairOffsets[pse]; }; RepairOffsets: PUBLIC PROC [tree: Tree, pse: LONG POINTER TO PathStkEntry] = { CheckProgression: PROC [offset1, offset2: PageOffset] RETURNS [ok: BOOL] = { RETURN [SELECT TRUE FROM offset2=entry0Offset => offset1=nilOffset, offset2=entry1Offset => offset1=entry0Offset, offset1>=offset2 => FALSE, ENDCASE => offset1+tree.BTreeEntrySize[@pagePtr[offset1]] = offset2]; }; pagePtr: BTreePagePtr = tree.ReferencePage[pse.pageNumber]; IF ~(CheckProgression[pse.lastOffset, pse.offset] AND CheckProgression[pse.nextToLastOffset, pse.lastOffset]) THEN { newOffset: PageOffset _ entry1Offset; lastOffset: PageOffset _ entry0Offset; nextToLastOffset: PageOffset _ nilOffset; WHILE newOffset#pse.offset DO nextToLastOffset _ lastOffset; lastOffset _ newOffset; newOffset _ newOffset+tree.BTreeEntrySize[@pagePtr[newOffset]]; ENDLOOP; pse.lastOffset _ lastOffset; pse.nextToLastOffset _ nextToLastOffset; }; tree.ReleasePage[pse.pageNumber]; }; PathToMaxDescendant: PUBLIC PROC [tree: Tree, pathStk: PathStk, page: PageNumber] = { pse: LONG POINTER TO PathStkEntry; pagePtr: BTreePagePtr; WHILE page#nilPage DO pathStk.top _ pathStk.top+1; pse _ @pathStk.path[pathStk.top]; pse.pageNumber _ page; pagePtr _ tree.ReferencePage[page]; pse.offset _ nilOffset+(tree.state.pageSize-pagePtr.freeWords); pse.lastOffset _ entry1Offset; pse.nextToLastOffset _ entry0Offset; tree.RepairOffsets[pse]; AssignRefESR[@pse.eslFront, NIL]; AssignRefESR[@pse.eslRear, NIL]; pse.leastSon _ nilPage; page _ pagePtr[pse.lastOffset].grPage; tree.ReleasePage[pse.pageNumber]; ENDLOOP; }; WriteStatePage: PUBLIC PROC [tree: Tree, update: UpdateState _ unchanged] = { statePtr: LONG POINTER TO TreeState = tree.ReferencePage[statePage, write]; statePtr^ _ tree.state; tree.ReleasePage[statePage, update]; }; Lock: PUBLIC ENTRY PROC [tree: Tree, mode: LockMode, checkState: BOOL _ TRUE] = { DO SELECT tree.objectState FROM closed => IF checkState THEN RETURN WITH ERROR Error[closed]; suspended => IF checkState THEN {WAIT tree.unlocked; LOOP}; open => NULL ENDCASE; SELECT mode FROM read => IF tree.lockCount>=0 THEN {tree.lockCount _ tree.lockCount+1; EXIT}; update => IF tree.lockCount=0 THEN {tree.lockCount _ -1; EXIT}; ENDCASE; WAIT tree.unlocked; -- wait for tree to be unlocked ENDLOOP; }; Unlock: PUBLIC ENTRY PROC [tree: Tree] = { IF tree.lockCount=0 THEN ERROR Bug[treeNotLocked]; tree.lockCount _ MAX[tree.lockCount-1, 0]; IF tree.lockCount=0 THEN BROADCAST tree.unlocked; -- BROADCAST, not NOTIFY, because releasing a write lock might make multiple readers eligible to enter simultaneously. }; GetHeapAndTable: PUBLIC ENTRY PROC [tree: Tree, pathStk: PathStk] = { pathStk.heap _ tree.heap; tree.heap _ NIL; pathStk.entryTable _ tree.entryTable; tree.entryTable _ NIL; IF pathStk.heap#NIL THEN heapsReused _ heapsReused+1 ELSE { pathStk.heap _ NEW[Heap[tree.maxRecordsPerPage+1]]; -- heap holds at most one page's worth of entries at any given time pathStk.entryTable _ NEW[EntryTable[3*tree.maxRecordsPerPage+1]]; -- may have to index scroll containing up to 3 pages' worth of entries heapsCreated _ heapsCreated+1; }; }; ReturnHeapAndTable: PUBLIC ENTRY PROC [tree: Tree, pathStk: PathStk] = { IF tree.heap=NIL THEN { tree.heap _ pathStk.heap; tree.entryTable _ pathStk.entryTable }; pathStk.heap _ NIL; pathStk.entryTable _ NIL; }; GetDefaultPathStk: PUBLIC ENTRY PROC [tree: Tree] RETURNS [pathStk: PathStk] = { pathStk _ tree.defaultPathStk; tree.defaultPathStk _ NIL; IF pathStk=NIL THEN { pathStk _ NewPathStk[]; pathStksCreated _ pathStksCreated+1 } ELSE { pathStk.version _ LAST [CARD]; pathStksReused _ pathStksReused+1 }; }; ReturnDefaultPathStk: PUBLIC ENTRY PROC [tree: Tree, pathStk: PathStk] = { IF tree.defaultPathStk=NIL THEN tree.defaultPathStk _ pathStk; }; Error: PUBLIC SAFE ERROR [reason: Reason] = CODE; UpdateInProgress: PUBLIC SAFE SIGNAL = CODE; Bug: PUBLIC ERROR [type: BugType] = CODE; PathToPreviousEntry: PROC [tree: Tree, pathStk: PathStk] RETURNS [ok: BOOL] = { pagePtr: BTreePagePtr; pse: LONG POINTER TO PathStkEntry _ @pathStk.path[pathStk.top]; IF pathStk.top=0 THEN RETURN [FALSE]; tree.BackUpOneEntry[pse]; [ptr: pagePtr, pse: pse] _ tree.ReferenceStack[pathStk]; IF pagePtr[pse.lastOffset].grPage=nilPage THEN { WHILE pse.lastOffsetM˜Mšœ˜Mšžœžœžœ˜˜>Mšœ˜—šžœ˜šžœ˜MšœM™MMšœ!˜!Mšžœžœžœ˜6Mšœ žœ˜šžœ#žœ˜+Mšœ1™1Mšœžœžœ˜Mšžœ ˜Mšœ˜—Mšœ žœ˜Mšœ˜—šžœ˜Mšœ™M˜-M˜˜>M˜Mšžœ˜—Mšœ˜——M˜&M˜Mšžœ˜—Mšžœ žœžœ"˜5šž˜Mšœžœ˜—Mšœ˜—Mšžœžœ$˜9M˜Mšœ˜—šŸœžœžœžœžœžœ žœžœ žœ˜M˜šžœžœ*ž˜CMšžœžœ˜M˜Mšœ2žœ˜8šžœžœžœ$žœ˜PM˜"MšœJ˜Jšžœž˜Mšœ žœ)˜:Mšžœ)žœžœžœ˜QMšžœžœžœ ˜3M˜Mšžœ˜—Mšœ˜—M˜šž˜Mšœžœ˜Mšžœžœ˜—Mšžœ˜—M˜Mšœ˜—š Ÿ œžœžœžœžœžœ˜CMšœ žœ˜Mšœžœžœ  ˜*Mšœ˜——šœ™šŸ œžœžœ;žœžœžœ žœ˜šŸ œžœžœ˜6MšœΨ™ΨM˜SMšœžœžœžœ˜WM˜%Mšœ˜MšœΑ™Αšžœžœžœž˜šžœžœ˜Mšžœžœ5žœžœ˜^Mšœ˜M˜Mšœ˜—MšœA˜AM˜M˜Mšžœ˜—Mšžœžœžœ˜>Mšœί™ίšžœž˜ Mšœ2˜2šžœ3ž˜=M˜Mšœžœžœ˜&M˜Mšžœ˜—Mšžœ˜—Mšžœ˜ Mšœ˜—Mšœžœžœžœ˜"M˜M˜M˜Mšœžœ˜š žœžœžœžœžœžœ˜aMšœC™CM˜Mšžœ žœžœ  0˜NMšžœ(žœžœ˜IM˜8šžœ3ž˜=Mšœžœ  ˜-Mšœžœ ˜%šœ ˜ šžœžœ@žœ5˜Mšžœžœ ,˜6Mšžœžœ $˜B—Mšœ˜—Mšžœ˜—M˜!Mšžœžœžœ˜Mšžœ žœ˜Mšœ˜—Mšœ™M˜M˜M˜#M˜M˜M˜M˜ M˜M˜šžœž˜Mšœ˜Mšœ&˜&M˜Mšœ žœ˜M˜šœ˜MšžœG˜M—M˜!M˜'Mšœ-˜-Mšœ˜Mšžœ žœ ˜AMšœžœ˜!Mšœžœ˜ M˜)Mšœ!˜!Mšžœ˜—M˜M˜Mšœ%˜%Mšœ˜—šŸœžœžœ<žœžœžœžœ ˜”Mšœ!˜!Mšœ/˜/Mšœ˜—š Ÿœžœžœžœžœžœ˜OM˜M˜M˜Mšœ˜—š Ÿ œžœžœžœžœžœ˜NšŸœžœ žœžœ˜Lšžœžœžœž˜M˜*M˜-Mšœžœ˜Mšžœ>˜E—Mšœ˜—M˜;šžœ0žœ9žœ˜tM˜%M˜&M˜)šžœž˜M˜M˜Mšœ?˜?Mšžœ˜—M˜M˜(Mšœ˜—M˜!Mšœ˜—šŸœžœžœ5˜UMšœžœžœžœ˜"M˜šžœž˜M˜M˜!Mšœ˜Mšœ#˜#M˜?M˜M˜$M˜Mšœžœ˜!Mšœžœ˜ M˜Mšœ&˜&Mšœ!˜!Mšžœ˜—Mšœ˜—šŸœžœžœ2˜MMšœ žœžœžœ2˜KM˜Mšœ$˜$Mšœ˜—š Ÿœžœžœžœ*žœžœ˜Qšž˜šžœž˜Mš œ žœ žœžœžœžœ˜=Mš œ žœ žœžœžœ˜;Mšœž˜ Mšžœ˜—šžœž˜˜Mšžœžœ%žœ˜D—˜ Mšžœžœžœ˜5—Mšžœ˜—Mšžœ ˜3Mšžœ˜—Mšœ˜—šŸœžœžœžœ˜*Mšžœžœžœ˜2Mšœžœ˜*Mš žœžœž œ Πck  ‘ ^˜¨Mšœ˜—šŸœžœžœžœ#˜EM˜Mšœ žœ˜Mšœ%˜%Mšœžœ˜Mšžœžœžœ˜4šžœ˜Mšœžœ" C˜wMšœžœ* F˜ˆMšœ˜Mšœ˜—Mšœ˜—šŸœžœžœžœ#˜Hšžœ žœž˜MšœC˜C—Mšœžœ˜Mšœžœ˜Mšœ˜—š Ÿœžœžœžœžœ˜PMšœ˜Mšœžœ˜Mšžœ ž˜Mšžœ@˜DMšžœžœžœ'˜JMšœ˜—šŸœžœžœžœ#˜JMšžœžœžœ˜>Mšœ˜—Mš œžœžœžœžœ˜1Mš œžœžœžœžœ˜,Mšœžœžœžœ˜)—šœ™šŸœžœ žœžœ˜OMšœIžœ žœ™|Mšœ˜Mšœžœžœžœ+˜?Mšžœžœžœžœ˜%M˜Mšœ8˜8šžœ'˜)šžœ˜Mšœ™šžœž˜$Mšœ<™