DIRECTORY Atom, Basics, BTree, BTreeVM, Commander, CommandTool, FS, FSBackdoor, IO, Log, LupineRuntime, PrincOps, PrincOpsUtils, ProcessProps, RefTab, RefText, Rope, ThNet, UserProfile; WPBTreeImpl: PROGRAM IMPORTS Atom, Basics, BTree, BTreeVM, Commander, CommandTool, FS, FSBackdoor, IO, Log, LupineRuntime, RefTab, PrincOpsUtils, ProcessProps, RefText, Rope, ThNet, UserProfile EXPORTS ThNet SHARES Rope = BEGIN OPEN IO; ROPE: TYPE = Rope.ROPE; Attrs: TYPE = ThNet.Attrs; WPListing: TYPE = ThNet.WPListing; wpRepPrim: BTree.RepresentationPrimitives = [ compare: Compare, compareEntries: CompareEntries, entrySize: EntrySize, entryFromRecord: EntryFromRecord, newRecordFromEntry: NewRecordFromEntry]; Key: TYPE = ROPE; Record: TYPE = REF WPEntryRecord; Entry: TYPE = LONG POINTER TO WPEntryRecord; WPEntryRecord: TYPE = RECORD [ recordSize: EntSize, linkIndex: INT_-1, -- If non-negative, word index of an additional record in another file. recordData: SEQUENCE COMPUTED EntSize OF INTEGER ]; WPState: TYPE = ThNet.WPState; WPStateRecord: TYPE = ThNet.WPStateRecord; EntSize: TYPE = [0..LAST[BTree.PageSize]-BTree.reservedWordsPerPage+1]; lastEnt: EntSize = LAST[EntSize]-1; Compare: PROCEDURE [key: BTree.Key, entry: Entry] RETURNS [BTree.Comparison] = { recordKey: Key=RopeFromWPEntryRecord[entry,0].rope; RETURN [CompareRopes[NARROW[key, Key], recordKey, FALSE]]; }; CompareEntries: PROCEDURE [entry1, entry2: Entry] RETURNS [BTree.Comparison] = { e1: Key=RopeFromWPEntryRecord[entry1,0].rope; e2: Key=RopeFromWPEntryRecord[entry2,0].rope; RETURN [CompareRopes[e1, e2, FALSE]]; }; EntrySize: PROCEDURE [entry: Entry] RETURNS [words: BTree.EntSize] = { RETURN [SIZE[WPEntryRecord[entry.recordSize]]] }; EntryFromRecord: PROCEDURE [record: BTree.Record] RETURNS [entry: Entry] = { RETURN [LOOPHOLE[NARROW[record, Record]]] }; NewRecordFromEntry: PROCEDURE [entry: Entry] RETURNS [record: Record] = BEGIN record _ NEW[WPEntryRecord[entry.recordSize]]; PrincOpsUtils.LongCopy[to: LOOPHOLE[record], from: entry, nwords: SIZE[WPEntryRecord[entry.recordSize]]]; END; CompareRopes: PROC[s1: ROPE, s2: ROPE, substringEqual: BOOL] RETURNS[res: BTree.Comparison] = { ss1: INT=Rope.Size[s1]; r2: ROPE_IF substringEqual THEN Rope.Substr[s2, 0, ss1] ELSE s2; RETURN [Rope.Compare[s1, r2, FALSE]]; }; wpAttrs: RefTab.Ref; wpExtInt: RefTab.Ref; InitWhitePagesDatabase: PUBLIC SAFE PROC[ fileName: ROPE, accessOptions: FS.AccessOptions _ $read, howToComplain: Log.WhereToReport_$System] RETURNS[wpState: WPState_NIL] = TRUSTED { buffer: REF TEXT = RefText.ObtainScratch[100]; treeName: ROPE=fileName.Concat[".Tree"]; dataName: ROPE=fileName.Concat[".TreeData"]; feepTreeName: ROPE=fileName.Concat[".FTree"]; treeFile, dataFile, feepFile: FS.OpenFile; keyRecord, feepKeyRecord: Record; IF wpAttrs=NIL THEN { wpAttrs _ RefTab.Create[]; FOR attr: Attrs IN Attrs DO IF keysForAttributes[attr] # $unassigned THEN []_RefTab.Store[wpAttrs, keysForAttributes[attr], NEW[Attrs_attr]]; ENDLOOP; }; IF wpExtInt=NIL THEN { s: IO.STREAM = FS.StreamOpen[UserProfile.Token[ key: "ThrushWPMappings", default: "/Indigo/Voice/BTree/IntExt.map"]]; wpExtInt _ RefTab.Create[]; WHILE ~s.EndOf[] DO ENABLE IO.EndOfStream=>EXIT; int: ATOM _ Atom.MakeAtomFromRefText[s.GetCedarToken[buffer: buffer].token]; ext: ATOM; []_s.GetChar[]; ext _ Atom.MakeAtomFromRefText[s.GetLine[buffer]]; []_wpExtInt.Store[ext, int]; -- external version is the key. ENDLOOP; s.Close[]; }; RefText.ReleaseScratch[buffer]; wpState _ NEW[WPStateRecord]; keyRecord _ NEW[WPEntryRecord[LAST[EntSize]]]; wpState.keyRecord _ keyRecord; feepKeyRecord _ NEW[WPEntryRecord[LAST[EntSize]]]; wpState.feepKeyRecord _ feepKeyRecord; wpState.wpTree _ BTree.New[repPrim: wpRepPrim, storPrim: [BTreeVM.ReferencePage, BTreeVM.ReleasePage], minEntrySize: 4]; wpState.wpFeepTree _ BTree.New[repPrim: wpRepPrim, storPrim: [BTreeVM.ReferencePage, BTreeVM.ReleasePage], minEntrySize: 4]; { ENABLE FS.Error => IF error.group = user THEN { Log.Problem[error.explanation, howToComplain]; treeFile_FS.nullOpenFile; CONTINUE; }; SELECT accessOptions FROM $read => { treeFile_FS.Open[name: treeName, remoteCheck: FALSE]; feepFile_FS.Open[name: feepTreeName, remoteCheck: FALSE]; dataFile _ FS.Open[name: dataName, remoteCheck: FALSE]; }; $create => { treeFile_FS.Create[name: treeName, keep: 2, setKeep: TRUE, pages: 100]; feepFile_FS.Create[name: feepTreeName, keep: 2, setKeep: TRUE, pages: 100]; dataFile _ FS.Create[name: dataName, keep: 2, setKeep: TRUE, pages: 300]; }; $write => { treeFile_FS.OpenOrCreate[name: treeName, keep: 2, pages: 100]; feepFile_FS.OpenOrCreate[name: feepTreeName, keep: 2, pages: 100]; dataFile _ FS.OpenOrCreate[name: dataName, keep: 2, pages: 300]; }; ENDCASE => Log.Problem["Don't request $append option for BTree", howToComplain]; }; IF treeFile=NIL OR dataFile = NIL OR feepFile=NIL THEN RETURN[NIL]; wpState.wpDataVMFile _ dataFile; wpState.wpDataVMLen _ FS.GetInfo[dataFile].bytes/2; wpState.wpVM _ BTreeVM.Open[file: FSBackdoor.GetFileHandle[treeFile], filePagesPerPage: 1, cacheSize: 20]; wpState.wpFeepVM _ BTreeVM.Open[file: FSBackdoor.GetFileHandle[feepFile], filePagesPerPage: 1, cacheSize: 20]; wpState.wpDataVM _ BTreeVM.Open[file: FSBackdoor.GetFileHandle[dataFile], filePagesPerPage: 1, cacheSize: 10]; []_OpenWhitePagesDatabase[wpState, howToComplain, FS.GetInfo[dataFile].bytes=0]; }; OpenWhitePagesDatabase: SAFE PROC[ wpState: WPState, howToComplain: Log.WhereToReport_$System, init: BOOL_FALSE] RETURNS [ok: BOOL_FALSE] = TRUSTED { ENABLE BTree.Error => { Log.Problem["Trouble opening BTree", howToComplain]; CONTINUE }; IF wpState=NIL THEN RETURN[FALSE]; IF wpState.wpOpen THEN RETURN; wpState.wpTree.Open[storage: wpState.wpVM, pageSize: FS.WordsForPages[1], initialize: init]; wpState.wpFeepTree.Open[storage: wpState.wpFeepVM, pageSize: FS.WordsForPages[1], initialize: init]; ok _ wpState.wpOpen _ TRUE; }; CloseWhitePagesDatabase: PUBLIC SAFE PROC[wpState: WPState, howToComplain: Log.WhereToReport_$System] RETURNS [ok: BOOL_TRUE] = TRUSTED { IF wpState=NIL OR ~wpState.wpOpen THEN RETURN; wpState.wpTree.SetState[closed!BTree.Error => { ok_FALSE; CONTINUE}]; wpState.wpFeepTree.SetState[closed!BTree.Error => { ok_FALSE; CONTINUE}]; wpState.wpDataVM.FlushCache[]; wpState.wpDataVM.FreeBuffers[]; FS.SetByteCountAndCreatedTime[wpState.wpDataVMFile, wpState.wpDataVMLen*2!FS.Error => IF error.group=user THEN { Log.Problem[error.explanation, howToComplain]; CONTINUE; }]; wpState.wpDataVMValid _ TRUE; wpState.wpOpen _ FALSE; }; SetProperLength: SAFE PROC[name: ROPE] = CHECKED { file: FS.OpenFile _ FS.Open[name, $write]; pages: INT _ FS.GetInfo[file].pages; FS.SetByteCountAndCreatedTime[file, FS.BytesForPages[pages]]; FS.Close[file]; }; WhitePagesLookup: PUBLIC SAFE PROC[ wpState: WPState, name: ROPE, feep: BOOL_FALSE ] RETURNS [ listing: WPListing_NIL ] = TRUSTED { tree: BTree.Tree _ IF feep THEN wpState.wpFeepTree ELSE wpState.wpTree; FindOne: SAFE PROC[record: BTree.Record] RETURNS [continue: BOOLEAN_FALSE] = TRUSTED { index: EntSize; key: ATOM; value: ROPE; entry: Entry _ EntryFromRecord[record]; [index, key, value] _ RopeFromWPEntryRecord[entry, 0]; SELECT CompareRopes[name, RopeFromWPEntryRecord[entry,0].rope, TRUE] FROM less => RETURN[TRUE]; equal => NULL; ENDCASE => RETURN[FALSE]; IF feep AND listing#NIL THEN { listing _ NIL; RETURN; }; IF index=lastEnt OR (record _ GetLinkedRecord[wpState, NARROW[record], $read]) = NIL THEN RETURN[feep]; entry _ EntryFromRecord[record]; listing _ RefTab.Create[]; index _ 0; WHILE index#lastEnt DO []_listing.Store[key, value]; [index, key, value] _ RopeFromWPEntryRecord[entry, index]; ENDLOOP; RETURN[feep]; }; IF ~wpState.wpOpen AND ~OpenWhitePagesDatabase[wpState] THEN RETURN; []_BTree.EnumerateRecords[tree: tree, key: name, relation: less, Proc: FindOne]; }; WhitePagesEntry: PUBLIC SAFE PROC[ wpState: WPState, name: ROPE_NIL, key: ATOM_$officeNumber, feep: BOOL_FALSE, listing: WPListing _ NIL] RETURNS [fullRName: ROPE_NIL, entry: ROPE_NIL, newListing: WPListing_NIL] = TRUSTED { IF listing = NIL THEN listing _ WhitePagesLookup[wpState, name, feep]; IF listing = NIL THEN RETURN; newListing _ listing; fullRName _ NARROW[listing.Fetch[$rName].val]; IF key#NIL THEN entry _ NARROW[listing.Fetch[key].val]; }; WhitePagesEnter: PUBLIC SAFE PROC[wpState: WPState, listing: WPListing] = TRUSTED { IF ~wpState.wpOpen AND ~OpenWhitePagesDatabase[wpState] THEN RETURN; WhitePagesDo[wpState, listing, enter]; }; WhitePagesPrint: PUBLIC SAFE PROC[wpState: WPState, listing: WPListing] = TRUSTED { WhitePagesDo[wpState, listing, print]; }; WPFn: TYPE = { print, enter, note }; WhitePagesDo: SAFE PROC[wpState: WPState, listing: WPListing, fn: WPFn] = TRUSTED { dataPage: BTree.PagePtr; dummyData: ARRAY [0..4) OF CARDINAL; dataEntry: Entry _ LOOPHOLE[LONG[@dummyData[0]]]; keyEntry: Entry _ EntryFromRecord[wpState.keyRecord]; feepKeyEntry: Entry _ EntryFromRecord[wpState.feepKeyRecord]; entry: Entry _ keyEntry; name, feepName: ROPE; linkIndex: INT _ keyEntry.linkIndex; linkPage: INT; inPageIndex: INT; refType: BTree.ReferenceType _ $write; doIt: BOOL_FALSE; EPA: RefTab.EachPairAction = TRUSTED { realKey: ATOM _ NARROW[key,ATOM]; SELECT fn FROM enter => RopeToWPEntryRecord[entry, realKey, NARROW[val], doIt]; print => PrintWPEntry[realKey, NARROW[val]]; ENDCASE; RETURN[FALSE]; }; name_NARROW[listing.Fetch[$rName].val]; feepName _ ThNet.FeepName[name]; IF name=NIL THEN ERROR; keyEntry.recordSize_0; feepKeyEntry.recordSize_0; SELECT fn FROM enter => { RopeToWPEntryRecord[keyEntry, $rName, name, TRUE]; RopeToWPEntryRecord[feepKeyEntry, $rName, feepName, TRUE]; }; note => { (GetCommanderHandle[]).out.Put[rope[name], rope["\n"]]; RETURN; }; ENDCASE; keyEntry[keyEntry.recordSize] _ 0; keyEntry.recordSize _ keyEntry.recordSize + (1+3); -- 1 for the ending 0, 2 for the header feepKeyEntry[feepKeyEntry.recordSize] _ 0; feepKeyEntry.recordSize _ feepKeyEntry.recordSize + (1+3); dataEntry.recordSize_0; entry _ dataEntry; []_RefTab.Pairs[listing, EPA]; dataEntry.recordSize_dataEntry.recordSize + (1+3); IF fn=enter THEN { linkIndex _ wpState.wpDataVMLen; IF ~([linkPage, inPageIndex, ]_GetLinkValues[wpState, linkIndex]).ok THEN RETURN; IF inPageIndex=0 OR inPageIndex+dataEntry.recordSize>FS.WordsForPages[1] THEN { refType _ $new; IF inPageIndex#0 THEN { linkPage_SUCC[linkPage]; inPageIndex _ 0; linkIndex _ FS.WordsForPages[linkPage]; }; }; dataPage _ BTreeVM.ReferencePage[wpState.wpDataVM, linkPage, refType]; entry _ dataEntry _ dataPage+inPageIndex; dataEntry.recordSize _ 0; doIt _ TRUE; []_RefTab.Pairs[listing, EPA]; dataEntry[dataEntry.recordSize] _ 0; dataEntry.recordSize _ dataEntry.recordSize + (1+3); dataEntry.linkIndex _ -1; wpState.wpDataVMLen _ linkIndex+dataEntry.recordSize; BTreeVM.ReleasePage[wpState.wpDataVM, linkPage, endOfUpdate]; keyEntry.linkIndex _ linkIndex; feepKeyEntry.linkIndex _ linkIndex; BTree.UpdateRecord[tree: wpState.wpTree, key: name, record: NARROW[wpState.keyRecord]]; BTree.UpdateRecord[ tree: wpState.wpFeepTree, key: feepName, record: NARROW[wpState.feepKeyRecord]]; }; }; PrintWPEntry: PROC[key: ATOM, val: ROPE] = { h: Commander.Handle = GetCommanderHandle[]; h.out.PutF["%g: %g\n", atom[key], rope[val]]; IF key=$rName AND h.out#h.err THEN h.err.Put[rope[val], rope["\n"]]; }; GetCommanderHandle: PROC RETURNS[handle: Commander.Handle] = { p: Atom.PropList = ProcessProps.GetPropList[]; IF p=NIL THEN RETURN[NIL]; RETURN[NARROW[Commander.GetProperty[$CommanderHandle, p]]]; }; GetLinkedRecord: SAFE PROC[wpState: WPState, keyRecord: Record, access: FS.AccessOptions] RETURNS[record: Record_NIL] = TRUSTED { linkIndex: INT _ keyRecord.linkIndex; linkPage: INT; inPageIndex: INT; entry: Entry; pagePtr: BTree.PagePtr; IF ([linkPage, inPageIndex,]_GetLinkValues[wpState, linkIndex]).ok=FALSE THEN RETURN; pagePtr _ BTreeVM.ReferencePage[wpState.wpDataVM, linkPage, SELECT access FROM $read => $read, $create => $new, ENDCASE => $write]; entry _ pagePtr+inPageIndex; record _ NewRecordFromEntry[entry]; BTreeVM.ReleasePage[wpState.wpDataVM, linkPage, endOfUpdate]; }; GetLinkValues: SAFE PROC[wpState: WPState, linkIndex: INT] RETURNS[linkPage, inPageIndex: INT, ok: BOOL_FALSE] = TRUSTED { IF linkIndex<0 OR linkIndex> wpState.wpDataVMLen THEN { Log.Problem["BTree structure error"]; RETURN; }; ok_TRUE; linkPage _ FS.PagesForWords[linkIndex+1]-1; inPageIndex _ linkIndex - FS.WordsForPages[linkPage]; }; RopeFromWPEntryRecord: PROC[entry: Entry, index: EntSize] RETURNS[newIndex: EntSize_lastEnt, key: ATOM, rope: ROPE] = { code: INTEGER; keyRope: ROPE; IF index=lastEnt THEN RETURN; code _ entry[index]; SELECT code FROM 0 => RETURN; <0 => { key _ keysForAttributes[LOOPHOLE[-code]]; index_index+1; }; ENDCASE => { [index, keyRope] _ RFromWPER[entry, index]; key _ Atom.MakeAtom[keyRope]; }; [newIndex, rope] _ RFromWPER[entry, index]; }; RFromWPER: PROC[entry: Entry, index: EntSize] RETURNS[newIndex: EntSize_lastEnt, rope: ROPE] = { length: INTEGER; wfc: LONG CARDINAL; textRope: Rope.Text; IF index=lastEnt THEN RETURN; length _ entry[index]; rope _ textRope _ Rope.NewText[size: length]; wfc _ LupineRuntime.WordsForChars[length]; PrincOpsUtils.LongCOPY[ from: @entry[index+1], to: BASE[DESCRIPTOR[textRope.text]], nwords: wfc ]; newIndex _ index+wfc+1; }; RopeToWPEntryRecord: PROC[entry: Entry, key: ATOM, rope: ROPE, doIt: BOOL] = { index: EntSize _ entry.recordSize; attr: REF Attrs _ NARROW[wpAttrs.Fetch[key].val]; IF attr#NIL THEN { IF doIt THEN entry[index]_-LOOPHOLE[attr^, INTEGER]; entry.recordSize _ index+1; } ELSE RToWPER[entry, Atom.GetPName[key], doIt]; RToWPER[entry, rope, doIt]; }; RToWPER: PROC[entry: Entry, rope: ROPE, doIt: BOOL] = { index: EntSize _ entry.recordSize; wfc: LONG CARDINAL; len: INTEGER _ rope.Length[]; IF doIt THEN entry[index] _ len; wfc _ LupineRuntime.WordsForChars[len]; IF doIt THEN PrincOpsUtils.LongCOPY[ to: @entry[index+1], from: BASE[DESCRIPTOR[Rope.Flatten[rope].text]], nwords: wfc ]; entry.recordSize _ index+wfc+1; }; ConcreteKey: PROCEDURE [key: BTree.Key] RETURNS [Key] = { RETURN [NARROW[key]] }; ConcreteEntry: PROCEDURE [entry: BTree.Entry] RETURNS [Entry] = { RETURN [entry] }; file: FS.OpenFile _ FS.nullOpenFile; WhitePagesFill: SAFE PROC[cmd: Commander.Handle, accessOptions: FS.AccessOptions_write] = TRUSTED { s: IO.STREAM; lineBuffer: REF TEXT = RefText.ObtainScratch[100]; tokenBuffer: REF TEXT = RefText.ObtainScratch[100]; listing: WPListing_NIL; wpState: WPState; numEntries: CARDINAL _ 0; argsL: LIST OF ROPE; IF ([argsL,] _ CommandTool.ParseToList[cmd, IO.SP]).length<2 THEN { Log.Problem["Not enough file names specified", $System]; argsL _ NIL; }; IF argsL=NIL THEN RETURN; s _ FS.StreamOpen[argsL.first]; wpState _ InitWhitePagesDatabase[argsL.rest.first, accessOptions]; IF wpState#NIL THEN WHILE ~s.EndOf[] DO ENABLE IO.EndOfStream=>EXIT; line: REF TEXT _ s.GetLine[buffer: lineBuffer]; lineS: IO.STREAM; extKey, key: ATOM_NIL; value: ROPE; keyVal: REF TEXT; KeyProc: IO.BreakProc = TRUSTED { RETURN[IF char=': THEN sepr ELSE other]; }; IF line.length=0 THEN { IF listing#NIL THEN { IF ~wpState.wpOpen AND ~OpenWhitePagesDatabase[wpState] THEN EXIT; WhitePagesDo[wpState, listing, note]; WhitePagesEnter[wpState, listing]; key _ extKey _ NIL; numEntries _ SUCC[numEntries]; IF Basics.BITSHIFT[numEntries, 12]=0 THEN []_CloseWhitePagesDatabase[wpState]; }; listing _ NIL; LOOP; }; IF listing = NIL THEN listing _ NewListing[]; lineS _ IO.TIS[line]; keyVal _ NIL; keyVal _ lineS.GetToken[KeyProc, tokenBuffer!IO.EndOfStream => CONTINUE].token; IF lineS.EndOf[] THEN { -- no key, extension of previous value. IF key#NIL THEN value _ value.Cat["\n", Rope.FromRefText[keyVal]]; } ELSE { extKey _ Atom.MakeAtomFromRefText[keyVal]; key _ NARROW[wpExtInt.Fetch[extKey].val]; IF key=NIL THEN key_extKey; []_lineS.GetChar[!IO.EndOfStream => CONTINUE]; []_lineS.SkipWhitespace[!IO.EndOfStream => CONTINUE]; value _ lineS.GetLineRope[!IO.EndOfStream => CONTINUE]; }; IF key#NIL THEN List[listing, key, value]; ENDLOOP; (GetCommanderHandle[]).err.PutRope["-- Updates done.\n"]; s.Close[]; RefText.ReleaseScratch[lineBuffer]; RefText.ReleaseScratch[tokenBuffer]; []_CloseWhitePagesDatabase[wpState]; }; NewListing: SAFE PROC RETURNS [WPListing] = TRUSTED { RETURN[RefTab.Create[]]; }; List: SAFE PROC[listing: WPListing, key: ATOM, value: ROPE] = TRUSTED { []_RefTab.Store[listing, key, value]; }; FillCmdInit: Commander.CommandProc = TRUSTED { WhitePagesFill[cmd, $create]; }; FillCmd: Commander.CommandProc = TRUSTED { WhitePagesFill[cmd, $write]; }; keysForAttributes: ARRAY ThNet.Attrs OF ATOM = [ $unassigned, $name, $rName, $officeNumber, $officeDDDNumber, $outsideNumber, $officeAddress, $officeLocation, $organization, $homeAddress, $frequency, $mailSystem, $primaryKey, $unassigned, $unassigned, $unassigned ]; Commander.Register["WPFill", FillCmdInit]; Commander.Register["WPRefill", FillCmd]; END. WPBTreeImpl.mesa Last Edited by: Swinehart, January 13, 1984 4:28 pm Reads and write white pages data base records. Representation primitives ThNet Procedures name must be in form rr.xxxxxxx Returns first listing for which name is a substring, case unimportant, of the listing Put in something new, or replace something old. Put in something new, or replace something old. Put in something new, or replace something old. Start Here New Page needed. Type "conversion" operations, callable from debugger Subsidiary procedures Κx– "Cedar" style˜headšœ™Ibody™3L™.code2šΟk ˜ M˜M˜Mšœ˜Mšœ˜M˜ M˜ Mšœ˜Mšœ ˜ M˜M˜M˜M˜ M˜M˜ M˜M˜Mšœ˜Mšœ˜Mšœ ˜ ——šœ ˜Mšœ7œl˜¬Mšœœ˜Mšœ˜Mšœœ˜Mšœœœ˜Mšœœ˜Mšœ œ˜"—™šœ-˜-Mšœ˜Mšœ˜Mšœ˜Mšœ!˜!Mšœ(˜(—Mšœœœ˜Mšœœœ˜!Mš œœœœœ˜,šœœ˜J˜J˜Jšœ œΟcG˜ZJšœ œœ œ˜0J˜—J˜Jšœ œ˜Jšœœ˜*Mšœ œœ/˜GMšœœ ˜#šΟnœ œ œ˜PMšœ3˜3Mšœœœ˜:Mšœ˜—šŸœ œœ˜PMšœ-˜-Mšœ-˜-Mšœœ˜%Mšœ˜—šŸ œ œœ˜DMšœœœ%˜3—šŸœ œœ˜JMšœœœœ˜.—šŸœ œœ˜GMš˜Mšœ œ"˜.Mšœœœ#˜iMšœ˜—J˜š Ÿ œœœœœ˜Mšœ œ7˜BMšœ œ3˜@M˜—MšœI˜PM˜—Mšœ œœ œœ œœœœ˜CM˜ M˜3Mšœj˜jMšœn˜nMšœn˜nMšœ2œ˜PJ˜—J˜šŸœœœ˜"JšœB œ˜MJšœœœœ˜$šœ˜Jšœ5œ˜@—Jš œ œœœœ˜"Jšœœœ˜šœ5œ˜IMšœ˜—šœ=œ˜QMšœ˜—Mšœœ˜J˜J˜—šŸœ œœ=œœœœ˜‰Jš œ œœœœ˜.Mšœ3œœ˜EMšœ7œœ˜IM˜Mšœ˜Mš œHœ œœ2œ˜­Mšœœ˜Mšœœ˜J˜J˜—š Ÿœœœœœ˜2Jšœœ œ˜*Jšœœœ˜$Jšœ"œ˜=Jšœ ˜J˜J˜—šŸœœœœ˜#Jšœœœœ˜0Jšœœœ˜.Jšœœœœ˜GJšœ™J™UšŸœœœœ œœœ˜VJ˜Jšœœ˜ Jšœœ˜ Jšœ'˜'J˜6šœ˜šœ%œ˜/Jšœœœ˜Jšœ œ˜Jšœœœ˜——Jš œœ œœ œœ˜8šœ˜šœ#œœ˜EJšœ˜ ——Jšœ ˜ Jšœ˜J˜ šœ˜J˜J˜:Jšœ˜—Jšœ˜ J˜—Jšœœ"œœ˜DJ˜PJ˜—J˜šŸœœœœ˜"Jš œœœœœœ˜LJšœœ˜Jšœ œœ œœœœ˜UJšœ œœ2˜GJšœ œœœ˜J˜Jšœ œ˜.Jšœœœ œ˜7J˜—J˜š Ÿœœœœ)œ˜SJ™/Jšœœ"œœ˜DJšœ&˜&J˜J˜—š Ÿœœœœ)œ˜SJ™/Jšœ&˜&J˜J˜—J˜$J˜šŸ œœœ3œ˜SJ™/J˜Jšœ œœœ˜$Jšœœœ˜1J˜5J˜=J˜Jšœœ˜Jšœ œ˜$Jšœ œ˜Jšœ œ˜J˜&J˜Jšœœœ˜J˜šœœ˜&Jšœ œœœ˜!šœ˜Jšœ-œ ˜@Jšœœ˜,Jšœ˜—Jšœœ˜J˜—J˜JšΟb ™ Jšœœ˜'Jšœ ˜ Jšœœœœ˜Jšœ˜J˜šœ˜šœ ˜ Jšœ,œ˜2Jšœ4œ˜:J˜—JšœBœ˜LJšœ˜—Jšœ"˜"Jšœ3ž'˜ZJšœ*˜*Jšœ:˜:J˜Jšœ˜J˜Jšœœ˜Jšœ2˜2šœ˜J˜ JšœCœœ˜Qšœœ"œœ˜OJ™J˜šœœ˜Jšœ œ ˜J˜Jšœ œ˜'J˜—J˜—J˜FJ˜)J˜Jšœœ˜ Jšœœ˜J˜$Jšœ4˜4J˜J˜5J˜=J˜J˜#Jšœ<œ˜Wšœ˜Jšœ1œ˜P—J˜—J˜J˜—šŸ œœœœ˜,J˜+J˜-Jšœ œ œ"˜DJ˜—J˜šŸœœœ˜>J˜.Jš œœœœœ˜Jšœœ.˜;J˜—J˜šŸœœœ.œ˜YJšœœœ˜'Jšœ œ˜%Jšœ œ˜Jšœ œ˜J˜ J˜JšœAœœœ˜U˜;Jšœœ"œ ˜G—Jšœ˜J˜#J˜=J˜—J˜šŸ œœœœ˜:Jš œœœœœ˜?šœ œ œ˜7Jšœ&œ˜-J˜—Jšœœ˜Jšœ œ˜+Jšœœ˜5J˜—J˜šŸœœ˜9Jšœ!œœ˜=Jšœœ˜Jšœ œ˜Jšœœœ˜Jšœ˜šœ˜Jšœœ˜ ˜Jšœœ ˜)J˜J˜—šœ˜ Jšœ+˜+J˜J˜——Jšœ+˜+J˜—J˜šŸ œœ˜-Jšœ"œ˜2Jšœœ˜Jšœœœ˜Jšœ˜Jšœœœ˜Jšœ˜J˜-J˜*˜J˜Jšœœ œ˜$J˜ J˜—J˜J˜—J˜š Ÿœœœœœœ˜NJšœ"˜"Jšœœ œ˜1šœœœ˜Jšœœœœ˜4Jšœ˜J˜—Jšœ*˜.J˜J˜—J˜šŸœœœœ˜7Jšœ"˜"Jšœœœ˜Jšœœ˜Jšœœ˜ J˜'šœœ˜$Jšœ˜Jšœœ œ˜0J˜ J˜—Jšœ˜J˜——šœ4™4šŸ œ œœ˜7Mšœœœ ˜—šŸ œ œœ ˜?Mšœœ ˜——šœ™Mšœ$˜$š Ÿœœœ'œœ˜cMšœœœ˜ Mšœ œœ˜2Mšœ œœ˜3Mšœœ˜M˜Mšœ œ˜Mšœœœœ˜šœ,œœ˜CMšœAœ˜H—Mšœœœœ˜Mšœœ˜MšœB˜Bšœ œ œ ˜'Mšœœœ˜Mšœœœ!˜/Mšœœœ˜Mšœ œ˜Mšœœ˜ Mšœœœ˜Mš œ œ œœœ œœ ˜Mšœœ˜šœ œœ˜Jšœœ"œœ˜BM˜%Mšœ"˜"Mšœœ˜Mšœ œ ˜Mšœœœ%˜NM˜—Mšœ œ˜Mšœ˜M˜—Mšœ œœ˜-Mšœœœ˜Mšœ œ˜ Mšœ-œœ˜Ošœœž'˜?Mšœœœ3˜BM˜—šœ˜M˜*Mšœœ˜)Mšœœœ ˜Mšœœœ˜.Mšœœœ˜5Mšœœœ˜7M˜—Mšœœœ˜*Mšœ˜—M˜9M˜ M˜#M˜$M˜$M˜—Mš Ÿ œœœœœœ˜Qš Ÿœœœœ œœ˜GM˜%M˜—šœ%œ˜.M˜M˜—šœ!œ˜*M˜M˜—šœœ œœ˜0M˜ΧM˜—M˜*M˜(Mšœ˜——…—BW