DIRECTORY Basics USING [BITSHIFT, LowHalf], Convert USING [CardFromRope, Error], FS USING [Error, StreamOpen], IntelHex, IO, LarkPrograms, List USING [Remove], Rope USING [Compare, Concat, Equal, Fetch, Find, Length, ROPE, Substr], TeleLoad; LarkProgramsImpl: CEDAR PROGRAM IMPORTS Basics, Convert, FS, IntelHex, IO, List, Rope, TeleLoad EXPORTS LarkPrograms = { programs: LIST OF REF ANY; nullItem: PUBLIC LarkPrograms.STObject _ ["???", 0, procedure]; monitor: LarkPrograms.Program; slaveMonitor: LarkPrograms.Program; Junk: SIGNAL = CODE; ReadProgramFromMBFile: PUBLIC PROC [mbFileName: Rope.ROPE, baseAddress: TeleLoad.CoreAddress, log: IO.STREAM _ NIL, addressSpace: TeleLoad.AddressSpace _ main, wDir: Rope.ROPE] RETURNS [p: LarkPrograms.Program] = TRUSTED { trueFileName: Rope.ROPE _ mbFileName; mbFileStream: IO.STREAM; data: PACKED ARRAY [0..TeleLoad.MaxByte) OF TeleLoad.Byte; count: NAT _ 0; nameLength: NAT; IWord: PROC RETURNS [x: CARDINAL] = TRUSTED { x _ LOOPHOLE[mbFileStream.GetChar[], CARDINAL]; x _ Basics.BITSHIFT[x, 8]; x _ x + LOOPHOLE[mbFileStream.GetChar[], CARDINAL]; RETURN[x]; }; IByte: PROC RETURNS [x: CARDINAL] = TRUSTED { x _ LOOPHOLE[mbFileStream.GetChar[], CARDINAL]; RETURN[x]; }; DataFlush: PROC = TRUSTED { cb: TeleLoad.CoreBlock; cb _ NEW[TeleLoad.CoreBlockObject[count]]; cb.address _ baseAddress; FOR i: CARDINAL IN [0..count) DO cb.data[i] _ data[i]; ENDLOOP; baseAddress _ baseAddress + count; count _ 0; p.program _ CONS[cb, p.program]; IF log # NIL THEN log.PutRope["! "]; }; p _ NEW[LarkPrograms.ProgramObject]; p.addressSpace _ addressSpace; IF trueFileName = NIL THEN { IF log # NIL THEN log.PutF["Bad file name\n"]; RETURN[NIL]; }; IF log # NIL THEN log.PutF[" ParseMB %g: ", IO.rope[trueFileName]]; IF Rope.Find[s1: trueFileName, s2: ".", pos1: 0, case: FALSE] = -1 THEN trueFileName _ Rope.Concat[trueFileName, ".mb"]; p.programName _ trueFileName; mbFileStream _ FS.StreamOpen[fileName: trueFileName, wDir: wDir ! FS.Error => TRUSTED { IF log # NIL THEN log.PutRope[" ... file not found\n"]; CONTINUE; }]; IF mbFileStream = NIL THEN RETURN[NIL]; { IF IWord[] # 4 THEN GOTO BadFormat; -- boilerplate IF IWord[] # 1 THEN GOTO BadFormat; -- memory number IF IWord[] # 8 THEN GOTO BadFormat; -- 8 bit memory nameLength _ 0; DO nameLength _ nameLength + 1; IF IByte[] = 0 THEN EXIT; ENDLOOP; IF nameLength MOD 2 # 0 THEN [] _ IByte[]; -- skip to even boundary IF IWord[] # 2 THEN GOTO BadFormat; -- set current memory and location IF IWord[] # 1 THEN GOTO BadFormat; -- memory number IF IWord[] # 0 THEN GOTO BadFormat; -- 8 bit memory DO SELECT IWord[] FROM 0 => EXIT; 1 => NULL; -- memory data; ENDCASE => GOTO BadFormat; IF IWord[] # 0 THEN GOTO BadFormat; -- source line number (not used) data[count] _ IByte[]; [] _ IByte[]; count _ count + 1; IF count >= TeleLoad.MaxByte THEN DataFlush[]; ENDLOOP; DataFlush[]; FOR i: TeleLoad.Registers8086 IN [AX..FL] DO p.startState.Regs[i] _ 0; ENDLOOP; p.startState.Regs[SP] _ 54256; -- D3F0 p.startState.Regs[IP] _ Basics.LowHalf[baseAddress]; IF log # NIL THEN log.PutRope["\n"]; EXITS BadFormat => { IF log # NIL THEN log.PutRope[" Bad MB file format\n"]; p _ NIL; }; }; mbFileStream.Close[]; }; ReadProgramFromDisk: PUBLIC PROC [objectFileName: Rope.ROPE, log: IO.STREAM _ NIL, addressSpace: TeleLoad.AddressSpace _ main, wDir: Rope.ROPE] RETURNS [p: LarkPrograms.Program] = TRUSTED { trueFileName: Rope.ROPE _ objectFileName; myEndOf: IntelHex.EndOfProc = TRUSTED { RETURN[objectFileStream.EndOf[]]; }; myGetByte: IntelHex.GetByteProc = TRUSTED { RETURN[objectFileStream.GetChar[] - 0C]; }; myGetPosition: IntelHex.GetPositionProc = TRUSTED { RETURN[LOOPHOLE[objectFileStream.GetIndex[], LONG CARDINAL]]; }; mySetPosition: IntelHex.SetPositionProc = TRUSTED { objectFileStream.SetIndex[LOOPHOLE[p, INT]]; }; myPutC: IntelHex.PutCharProc = TRUSTED { IF log # NIL THEN log.PutChar[c]; }; myPutEndRecord: IntelHex.PutEndRecordProc = TRUSTED { DataFlush[]; IF log # NIL THEN log.PutF[" ...End"]; }; myPutStartRecord: IntelHex.PutStartRecordProc = TRUSTED { DataFlush[]; IF log # NIL THEN log.PutF[" ...Start at %04x:%04x", IO.int[frame], IO.int[bytepos]]; p.startState[CS] _ frame; p.startState[IP] _ bytepos; }; myDataByte: IntelHex.DataByteProc = TRUSTED { IF gnaflag OR adr#gda THEN DataFlush[]; gdata[gptr] _ d; IF gptr = 0 THEN fda _ adr; gptr _ gptr+1; gda _ adr+1; IF gptr >= TeleLoad.locMaxByte THEN DataFlush[]; }; DataFlush: PROC = TRUSTED { cb: TeleLoad.CoreBlock; IF gptr=0 THEN RETURN; gnaflag _ FALSE; cb _ NEW[TeleLoad.CoreBlockObject[gptr]]; cb.address _ fda; FOR i: CARDINAL IN [0..gptr) DO cb.data[i] _ gdata[i]; ENDLOOP; gptr _ 0; p.program _ CONS[cb, p.program]; IF log # NIL THEN log.PutRope["! "]; }; gda: LONG CARDINAL _ LAST[LONG CARDINAL]; fda: LONG CARDINAL; gptr: CARDINAL _ 0; gdata: PACKED ARRAY [0..TeleLoad.MaxByte) OF TeleLoad.Byte; gnaflag: BOOLEAN _ TRUE; objectFileStream: IO.STREAM; { myIStream: IntelHex.IStream _ [ endof: myEndOf, get: myGetByte, GetPosition: myGetPosition, SetPosition: mySetPosition ]; myOStream: IntelHex.OStream _ [ PutEndRecord: myPutEndRecord, PutStartRecord: myPutStartRecord, DataByte: myDataByte ]; p _ NEW[LarkPrograms.ProgramObject]; p.addressSpace _ addressSpace; IF trueFileName = NIL THEN { IF log # NIL THEN log.PutF["Bad file name\n"]; RETURN[NIL]; }; IF log # NIL THEN log.PutF[" Parse %g: ", IO.rope[trueFileName]]; IF Rope.Find[s1: trueFileName, s2: ".", pos1: 0, case: FALSE] = -1 THEN trueFileName _ Rope.Concat[trueFileName, ".obj"]; p.programName _ trueFileName; objectFileStream _ FS.StreamOpen[fileName: trueFileName, wDir: wDir ! FS.Error => TRUSTED { IF log # NIL THEN log.PutRope[" ... file not found\n"]; CONTINUE; }]; IF objectFileStream = NIL THEN RETURN[NIL]; FOR i: TeleLoad.Registers8086 IN [AX..FL] DO p.startState.Regs[i] _ 0; ENDLOOP; p.startState.Regs[SP] _ 54256; -- D3F0 IF NOT IntelHex.ProcessFile[in: myIStream, out: myOStream, errs: myPutC] THEN { IF log # NIL THEN log.PutRope[" ...IntelHex Failure"]; p _ NIL; } ELSE { extension: INT _ Rope.Find[s1: p.programName, s2: ".obj", case: FALSE]; patchFileName: Rope.ROPE; IF extension # -1 THEN BuildSearchTable[program: p, name: Rope.Substr[base: p.programName, start: 0, len: extension], log: log, wDir: wDir]; IF p.searchTable # NIL AND log # NIL THEN log.PutF[" ... symbol table read"]; ResetPatchList[p]; patchFileName _ Rope.Concat[Rope.Substr[base: p.programName, start: 0, len: extension], ".patches"]; p.patchList _ PatchFromFile[program: p, fileName: patchFileName, log: log, wDir: wDir]; }; IF log # NIL THEN log.PutRope["\n"]; objectFileStream.Close[]; }; }; ResetPatchList: PUBLIC PROC [program: LarkPrograms.Program] = { IF program#NIL THEN program.patchList _ NIL; }; PatchProgramWord: PUBLIC PROC [program: LarkPrograms.Program, address: TeleLoad.CoreAddress, wordValue: CARDINAL, name: Rope.ROPE _ NIL] = { item: LarkPrograms.PatchItem _ NEW[LarkPrograms.PatchItemObject _ [name: name, address: address, wordValue: wordValue]]; program.patchList _ CONS[item, program.patchList]; }; Monitor: PUBLIC PROC [log: IO.STREAM _ NIL, addressSpace: TeleLoad.AddressSpace _ main, wDir: Rope.ROPE] RETURNS [mon: LarkPrograms.Program] = { IF addressSpace = main THEN { IF monitor # NIL THEN RETURN[monitor]; monitor _ ReadProgramFromDisk[objectFileName: "LarkMon", log: log, addressSpace: main, wDir: wDir]; IF monitor.nameTable = NIL OR monitor.searchTable = NIL THEN monitor _ NIL; mon _ monitor; } ELSE { IF slaveMonitor # NIL THEN RETURN[slaveMonitor]; slaveMonitor _ ReadProgramFromMBFile[mbFileName: "LarkSlave.mb", baseAddress: 0E000H, log: log, addressSpace: slave, wDir: wDir]; BuildSearchTable[program: slaveMonitor, name: "LarkSlave", log: log, wDir: wDir]; mon _ slaveMonitor; }; }; GetHex: PROC [h: IO.STREAM] RETURNS [n: LONG CARDINAL] = { r: Rope.ROPE _ h.GetTokenRope[].token; n _ Convert.CardFromRope[r, 16 ! Convert.Error => SIGNAL Junk]; }; BuildSearchTable: PUBLIC PROC [program: LarkPrograms.Program, name: Rope.ROPE, log: IO.STREAM _ NIL, wDir: Rope.ROPE] = {{ tabFile: IO.STREAM; count: INT; type: Rope.ROPE; program.searchTable _ NIL; program.nameTable _ NIL; tabFile _ FS.StreamOpen[fileName: name, wDir: wDir ! FS.Error => TRUSTED { tabFile _ NIL; CONTINUE; }]; IF tabFile = NIL THEN tabFile _ FS.StreamOpen[fileName: Rope.Concat[name, ".syms"], wDir: wDir ! FS.Error => TRUSTED { tabFile _ NIL; IF log # NIL THEN log.PutF["Cannot find %g or %g\n", IO.rope[name], IO.rope[Rope.Concat[name, ".syms"]]]; GOTO Die; }]; type _ tabFile.GetTokenRope[].token; IF NOT Rope.Equal[type, "Names", FALSE] THEN GOTO BadFormat; count _ tabFile.GetInt[]; program.nameTable _NEW[LarkPrograms.SymTabObject[count]]; FOR i: INT IN [0..count) DO program.nameTable[i].type _ IF Rope.Equal[tabFile.GetTokenRope[].token, "V"] THEN variable ELSE procedure; program.nameTable[i].addr _ GetHex[tabFile]; program.nameTable[i].id _ tabFile.GetTokenRope[].token; ENDLOOP; BubbleSortNameTable[program.nameTable]; type _ tabFile.GetTokenRope[].token; IF NOT Rope.Equal[type, "Addresses", FALSE] THEN GOTO BadFormat; count _ tabFile.GetInt[]; program.searchTable _ NEW[LarkPrograms.SymTabObject[count]]; FOR i: INT IN [0..count) DO program.searchTable[i].type _ IF Rope.Equal[tabFile.GetTokenRope[].token, "V"] THEN variable ELSE procedure; program.searchTable[i].addr _ GetHex[tabFile]; program.searchTable[i].id _ tabFile.GetTokenRope[].token; ENDLOOP; BubbleSortSearchTable[program.searchTable]; tabFile.Close[]; EXITS BadFormat => { IF log # NIL THEN log.PutF["Bad symbols format\n"]; program.searchTable _ NIL; program.nameTable _ NIL; NULL; }; Die => NULL; };}; BubbleSortSearchTable: PROC [searchTable: LarkPrograms.SymbolTable] = { anyswap: BOOL _ TRUE; temp: LarkPrograms.STObject; WHILE anyswap DO anyswap _ FALSE; FOR i: NAT IN [0..searchTable.length-1) DO IF searchTable[i].addr > searchTable[i+1].addr THEN { temp _ searchTable[i]; searchTable[i] _ searchTable[i+1]; searchTable[i+1] _ temp; anyswap _ TRUE; }; ENDLOOP; ENDLOOP; }; BubbleSortNameTable: PROC [nameTable: LarkPrograms.SymbolTable] = { anyswap: BOOL _ TRUE; temp: LarkPrograms.STObject; WHILE anyswap DO anyswap _ FALSE; FOR i: NAT IN [0..nameTable.length-1) DO IF Rope.Compare[nameTable[i].id, nameTable[i+1].id, FALSE] = greater THEN { temp _ nameTable[i]; nameTable[i] _ nameTable[i+1]; nameTable[i+1] _ temp; anyswap _ TRUE; }; ENDLOOP; ENDLOOP; }; ProcedureEnclosing: PUBLIC PROC [program: LarkPrograms.Program, addr: TeleLoad.CoreAddress, searchMonitor: BOOL _ TRUE, wDir: Rope.ROPE] RETURNS [s: LarkPrograms.STObject] = { u: NAT; l: NAT _ 0; i: NAT; query: INT = addr; IF program = NIL OR program.searchTable = NIL THEN { IF searchMonitor THEN RETURN[ProcedureEnclosing[program: Monitor[wDir: wDir], addr: addr, searchMonitor: FALSE, wDir: wDir]] ELSE RETURN[nullItem]; }; u _ program.searchTable.length - 1; DO i _ (l+u)/2; IF u < l THEN ERROR; IF program.searchTable[i].addr <= query THEN { IF i = (program.searchTable.length-1) THEN { IF searchMonitor THEN { s _ ProcedureEnclosing[program: Monitor[wDir: wDir], addr: addr, searchMonitor: FALSE, wDir: wDir]; IF s # nullItem THEN RETURN[s]; }; RETURN [program.searchTable[i]]; }; IF program.searchTable[i+1].addr > query THEN RETURN [program.searchTable[i]] ELSE l _ i + 1; } ELSE { IF i = 0 THEN RETURN [nullItem] ELSE u _ i - 1; }; ENDLOOP; }; FindVariable: PUBLIC PROC [program: LarkPrograms.Program, name: Rope.ROPE, searchMonitor: BOOL _ TRUE, wDir: Rope.ROPE] RETURNS [item: LarkPrograms.STObject, found: BOOL] = {{ u: NAT; l: NAT _ 0; i: NAT; IF program = NIL OR program.nameTable = NIL THEN GOTO TryMonitor; u _ program.nameTable.length - 1; DO i _ (l+u)/2; IF u < l THEN GOTO TryMonitor; SELECT Rope.Compare[name, program.nameTable[i].id, FALSE] FROM less => { IF i = 0 THEN GOTO TryMonitor ELSE u _ i - 1; }; greater => { IF i = (program.nameTable.length-1) THEN GOTO TryMonitor ELSE l _ i + 1; }; equal => RETURN [item: program.nameTable[i], found: TRUE]; ENDCASE => ERROR; ENDLOOP; EXITS TryMonitor => { IF searchMonitor THEN { [item: item, found: found] _ FindVariable[program: Monitor[wDir: wDir], name: name, searchMonitor: FALSE, wDir: wDir]; RETURN[item: item, found: found]; } ELSE { IF IsNumber[name] THEN { item.id _ "???"; item.addr _ Convert.CardFromRope[name, 16 ! Convert.Error => GOTO GiveUp]; item.type _ variable; RETURN[item: item, found: TRUE]; } ELSE RETURN[item: nullItem, found: FALSE]; }; }; }; EXITS GiveUp => RETURN[item: nullItem, found: FALSE]; }; IsNumber: PROC [r: Rope.ROPE] RETURNS [BOOL] = { FOR i: INT IN [0..r.Length[]) DO SELECT r.Fetch[i] FROM IN ['0..'9], IN ['a..'f], IN ['A..'F] => LOOP; ENDCASE => RETURN [FALSE]; ENDLOOP; RETURN[TRUE]; }; EnumeratePrograms: PUBLIC PROC [proc: PROC [program: LarkPrograms.Program] RETURNS [stop: BOOL]] = { list: LIST OF REF ANY _ programs; WHILE list # NIL DO IF proc[NARROW[list.first, LarkPrograms.Program]] THEN EXIT; list _ list.rest; ENDLOOP; }; FetchProgram: PUBLIC PROC [programName: Rope.ROPE] RETURNS [p: LarkPrograms.Program] = TRUSTED { searchName: Rope.ROPE _ programName; lmap: PROC [item: REF ANY] = TRUSTED { lp: LarkPrograms.Program _ NARROW[item]; IF Rope.Equal[lp.programName, searchName, FALSE] THEN p _ lp; }; IF Rope.Find[s1: searchName, s2: ".", pos1: 0, case: FALSE] = -1 THEN searchName _ Rope.Concat[searchName, ".obj"]; MyMap[list: programs, proc: lmap]; }; AddOrReplaceProgram: PUBLIC PROC [program: LarkPrograms.Program] = { old: LarkPrograms.Program _ FetchProgram[program.programName]; IF old # NIL THEN programs _ List.Remove[ref: old, list: programs]; programs _ CONS[program, programs]; IF monitor # NIL AND Rope.Equal[program.programName, monitor.programName, FALSE] THEN monitor _ program; }; MyMap: PROC [list: LIST OF REF ANY, proc: PROC [item: REF ANY]] = { WHILE list # NIL DO proc[list.first]; list _ list.rest; ENDLOOP; }; PatchFromFile: PUBLIC PROC [program: LarkPrograms.Program, fileName: Rope.ROPE, log: IO.STREAM _ NIL, wDir: Rope.ROPE] RETURNS [LIST OF LarkPrograms.PatchItem] = { id: Rope.ROPE; value: CARDINAL; offset: NAT _ 0; patFile: IO.STREAM; line: Rope.ROPE; lis: IO.STREAM; pl: LIST OF LarkPrograms.PatchItem _ NIL; item: LarkPrograms.STObject; found: BOOL; { IF log # NIL THEN log.PutF["Patches from file %g\n", IO.rope[fileName]]; patFile _ FS.StreamOpen[fileName: fileName, wDir: wDir ! FS.Error => TRUSTED { patFile _ NIL; GOTO GiveUp; }]; IF patFile = NIL THEN { IF log # NIL THEN log.PutF["Cannot find %g\n", IO.rope[fileName]]; RETURN[NIL]; }; DO { ENABLE { IO.Error => { IF log # NIL THEN log.PutRope["IO.Error!\n"]; pl _ NIL; GOTO CloseFile; }; IO.EndOfStream => GOTO CloseFile; }; IF patFile.EndOf[] THEN GOTO CloseFile; line _ IO.GetLineRope[patFile]; IF line.Length <= 1 THEN GOTO CloseFile; IF line.Fetch[0] = '- THEN { IF log # NIL THEN log.PutF["%g\n", IO.rope[line]]; GOTO TryNextLine; }; lis _ IO.RIS[rope: line, oldStream: lis]; id _ lis.GetTokenRope[].token; offset _ GetHex[lis]; value _ GetHex[lis]; [item: item, found: found] _ FindVariable[program, id,,wDir]; IF NOT found THEN { IF log # NIL THEN log.PutF[" %g: not found\n", IO.rope[id]]; RETURN [NIL]; } ELSE pl _ CONS[NEW[LarkPrograms.PatchItemObject _ [name: IF offset = 0 THEN id ELSE IO.PutFR["%g+%xH", IO.rope[id], IO.card[offset]], address: item.addr + offset, wordValue: value]], pl]; EXITS TryNextLine => NULL; }; ENDLOOP; EXITS CloseFile => patFile.Close[]; GiveUp => NULL; }; RETURN [pl]; }; }. April 25, 1983 12:20 pm, LCS, created from old LarkPrograms August 10, 1983 12:02 pm, LCS, bug in PatchProgramWord August 10, 1983 12:02 pm, LCS, added ReadProgramFromMBFile August 30, 1983 3:16 pm, LCS, added slaveMonitor LarkProgramsImpl.mesa Copyright Σ 1986, 1987 by Xerox Corporation. All rights reserved. Stewart, December 21, 1983 3:51 pm Last Edited by: Pier, May 30, 1984 12:36:27 pm PDT Swinehart, April 6, 1987 10:12:44 am PDT global variables Now read in the MB file! Write an end record. Write a start record. Autonmatically incorporate patches from a standard place GetHex: PROC [h: IO.Handle] RETURNS [LONG CARDINAL] = { r: Rope.ROPE _ h.GetToken[]; v: Convert.Value _ Convert.Parse[text: [rope[r]], template: [unsigned[base: 16]]].value; RETURN[NARROW[v, Convert.Value[unsigned]! ANY => SIGNAL Junk].unsigned]; }; Swinehart, December 17, 1986 5:13:10 pm PST Force root directory lookups. That's not as good as making LarkControl do the right thing about working directories, but it's better than having lookups be random. changes to: ReadProgramFromMBFile, ReadProgramFromDisk, BuildSearchTable, PatchFromFile Κ“˜šœ™IcodešœB™B—Jšœ"™"™2K™(—J˜šΟk ˜ Jšœœœ ˜!Jšœœ˜$Jšœœ˜J˜ Jšœ˜J˜ Jšœœ ˜Jšœœ/œ ˜GJ˜ J˜—Jšœœ˜Jšœœ œ˜?šœ˜J˜J˜—Jšœ™Jš œ œœœœ˜Jšœ œ0˜@Jšœ˜Jšœ#˜#J˜Jšœœœ˜J˜šΟnœœœœ*œœœ9œœœ˜ήJšœœ˜%Jšœœœ˜Jšœœœœ˜:Jšœœ˜Jšœ œ˜J˜š žœœœœœ˜-Jšœœœ˜/Jšœ œ˜Jšœœœ˜3Jšœ˜ J˜—š žœœœœœ˜.Jšœœœ˜/Jšœ˜ J˜—šž œœœ˜J˜Jšœœ"˜*Jšœ˜šœœœ ˜ J˜Jšœ˜—Jšœ"˜"J˜ Jšœ œ˜ Jšœœœ˜$˜J˜——Jšœœ˜$Jšœ˜šœœœ˜Jšœœœ˜.Jšœœ˜ J˜—Jšœœœœ˜FJšœ5œœ1˜xJ˜šœœ1œ œ˜WJšœœœ&˜7Jšœ˜ J˜—Jš œœœœœ˜'J™J˜Jšœ œœ Οc˜3Jšœ œœ Ÿ˜5Jšœ œœ Ÿ˜4J˜š˜J˜Jšœ œœ˜Jšœ˜—Jšœ œœŸ˜DJšœ œœ Ÿ"˜GJšœ œœ Ÿ˜5Jšœ œœ Ÿ˜4š˜šœ ˜Jšœœ˜ JšœœŸ˜Jšœœ ˜—Jšœ œœ Ÿ ˜EJ˜J˜ J˜Jšœœ ˜.Jšœ˜—J˜ Jš œœœœœœ˜OJšœœ Ÿ˜&Jšœœ ˜4Jšœœœ˜$Jš˜˜Jšœœœ&˜7Jšœœ˜J˜—J˜Jšœ˜J˜J˜—šžœœœœœœœ9œœœ˜½Jšœœ˜)J˜Jšœœœ˜LJ˜Jšœ"œœ%˜WJ˜šœ*œ˜3Jšœœœœ˜=J˜J˜—Jšœ*œœœ˜cJ˜Jš œœœœœ˜MJ˜Jšœ™šœ,œ˜5J˜ Jšœœœ˜&J˜J˜—Jšœ™šœ0œ˜9J˜ Jš œœœ$œ œ˜UJšœ œ ˜Jšœ œ ˜J˜J˜—šœ$œ˜-Jšœ œ œ ˜'J˜Jšœ œ ˜J˜J˜ Jšœœ ˜0J˜J˜—šž œœœ˜J˜Jšœœœ˜Jšœ œ˜Jšœœ!˜)J˜šœœœ ˜J˜Jšœ˜—J˜ Jšœ œ˜ Jšœœœ˜$˜J˜——Jš œœœœœœ˜)Jšœœœ˜Jšœœ˜Jšœœœœ˜;Jšœ œœ˜Jšœœœ˜J˜˜J˜J˜J˜J˜J˜J˜—˜J˜J˜!J˜J˜J˜—Jšœœ˜$Jšœ˜šœœœ˜Jšœœœ˜.Jšœœ˜ J˜—Jšœœœœ˜DJšœ5œœ2˜yJ˜šœœ1œ œ˜[Jšœœœ&˜7Jšœ˜ J˜—Jš œœœœœ˜+Jš œœœœœœ˜OJšœœ Ÿ˜&šœœCœ˜OJšœœœ%˜6Jšœœ˜Jšœ˜—šœ˜Jšœ œ2œ˜GJšœœ˜Jšœœv˜ŒJš œœœœœ$˜MJ™8Jšœ˜Jšœd˜dJšœW˜WJ˜—Jšœœœ˜$J˜J˜J˜J˜—šžœœœ$˜?Jšœ œœœ˜,J˜J˜—š žœœœKœ œœ˜ŒJšœœV˜xJšœœ˜2J˜J˜—šžœœœœœœ9œœ ˜šœœ˜Jšœ œœœ ˜&Jšœc˜cJš œœœœœ œ˜KJšœ˜J˜—šœ˜Jšœœœœ˜0Jšœ˜JšœQ˜QJšœ˜J˜—J˜—J˜šžœœœœœœœ˜:Jšœœ˜&Jšœ2œ˜?J˜—š žœœœ œœœ™7Jšœœ™J™XJšœœœœ™HJ™—J˜šžœœœ,œœœœ œ˜zJšœ œœ˜Jšœœ˜ Jšœ œ˜Jšœœ˜Jšœœ˜šœ œ)œ œ˜JJšœ œ˜Jšœ˜ J˜—š œ œœ œ?œ œ˜vJšœ œ˜Jš œœœ$œ œ#˜iJšœ˜ J˜—J˜$Jš œœœœœ ˜˜ Jšœœœ ˜Jšœ ˜J˜—˜ Jšœ"œœ ˜8Jšœ ˜J˜—Jšœ œ%œ˜:Jšœœ˜—Jšœ˜—Jš˜šœ˜šœœ˜Jšœcœ˜vJšœ˜!Jšœ˜—šœ˜šœœ˜J˜Jšœ=œ ˜JJ˜Jšœœ˜ J˜—Jšœœœ˜*J˜—J˜—J˜Jš˜Jšœ œœ˜/J˜—J˜š žœœ œœœ˜0šœœœ˜ šœ ˜Jšœ œ œ œ˜.Jšœœœ˜—Jšœ˜—Jšœœ˜J˜—J˜š žœœœœ!œœ˜dJš œœœœœ ˜!šœœ˜Jšœœ$œœ˜˜>Jšœœœ2˜CJšœ œ˜#Jš œ œœ6œœ˜hJ˜J˜—šžœœœœœœœœœ˜Cšœœ˜J˜J˜Jšœ˜—J˜—J˜šž œœœ0œœœœ œœœœ˜£Jšœ œ˜Jšœœ˜Jšœœ˜Jšœ œœ˜Jšœ œ˜Jšœœœ˜Jšœœœœ˜)Jšœ˜Jšœœ˜ J˜Jšœœœ$œ˜Hšœ œ-œ œ˜NJšœ œ˜Jšœ˜ J˜—šœ œœ˜Jšœœœœ˜BJšœœ˜ J˜—šœ˜šœ˜šœ ˜ Jšœœœ˜-Jšœœ˜ Jšœ ˜J˜—Jšœœ ˜!J˜—Jšœœœ ˜'Jšœœ˜Jšœœœ ˜(šœœ˜Jšœœœœ ˜2Jšœ ˜J˜—Jšœœœ˜)J˜J˜J˜Jšœ=˜=šœœœ˜Jšœœœœ ˜=Jšœœ˜ J˜—Jšœœœ'œ œœœœ œE˜»Jš˜Jšœœ˜J˜Jšœ˜—Jš˜J˜Jšœ œ˜J˜Jšœ˜ J˜—J˜J˜Jšœœ˜;Jšœœ˜6Jšœœ˜:Jšœœ˜0™+K™€Kšœ ΟrK™W—K™—…—=:V]