DIRECTORY Basics USING [CompareInt, LowHalf], Commander USING [CommandProc, Register], CommandTool USING [ArgumentVector, Failed, Parse], Convert USING [CardFromRope, Error], FS USING [Error, StreamOpen], IO, LarkPrograms, List USING [Reverse], RedBlackTree, Rope; MapParseImpl: CEDAR PROGRAM IMPORTS Basics, Commander, CommandTool, Convert, FS, IO, List, RedBlackTree, Rope = BEGIN BadHex: ERROR = CODE; BadID: ERROR = CODE; BadAddress: ERROR = CODE; Hex4: PROC [r: Rope.ROPE, pos: INT] RETURNS [res: INT] = { res _ (Hex1[r: r, pos: pos] * 4096) + (Hex1[r: r, pos: pos + 1] * 256); res _ res + (Hex1[r: r, pos: pos + 2] * 16) + Hex1[r: r, pos: pos + 3]; }; Hex2: PROC [r: Rope.ROPE, pos: INT] RETURNS [res: INT] = { res _ (Hex1[r: r, pos: pos] * 16) + Hex1[r: r, pos: pos + 1]; }; Hex1: PROC [r: Rope.ROPE, pos: INT] RETURNS [INT] = { c: CHAR _ r.Fetch[pos]; SELECT TRUE FROM Digit[c] => RETURN [c - '0]; c IN ['A..'F] => RETURN [(c - 'A) + 10]; ENDCASE => ERROR BadHex; }; Digit: PROC [c: CHAR] RETURNS [BOOL] = INLINE { RETURN [c IN ['0..'9]]; }; Letter: PROC [c: CHAR] RETURNS [BOOL] = INLINE { RETURN [c IN ['a..'z] OR c IN ['A..'Z]]; }; LowWord: PROC [i: INT] RETURNS [INT] = { c: CARDINAL _ Basics.LowHalf[i]; RETURN [c]; }; GetID: PROC [r: Rope.ROPE, pos: INT] RETURNS [Rope.ROPE] = { end: INT; c: CHAR; FOR i: INT IN [pos..r.Length[]) DO c _ r.Fetch[i]; IF NOT Letter[c] AND NOT Digit[c] THEN { IF c # '\n THEN ERROR BadID; end _ i; EXIT; }; REPEAT FINISHED => end _ r.Length[]; ENDLOOP; RETURN[Rope.Substr[base: r, start: pos, len: end - pos]]; }; GetMidID: PROC [r: Rope.ROPE, pos: INT] RETURNS [Rope.ROPE] = { end: INT; c: CHAR; FOR i: INT IN [pos..r.Length[]) DO c _ r.Fetch[i]; IF NOT Letter[c] AND NOT Digit[c] THEN { IF c # ' THEN ERROR BadID; end _ i; EXIT; }; REPEAT FINISHED => end _ r.Length[]; ENDLOOP; RETURN[Rope.Substr[base: r, start: pos, len: end - pos]]; }; GetVarAddress: PROC [r: Rope.ROPE, pos: INT] RETURNS [idAddr: INT] = { DO IF pos <= 15 THEN ERROR BadAddress; IF HexChar[r.Fetch[pos]] THEN { idAddr _ Hex2[r: r, pos: pos - 1 ! BadHex => ERROR BadAddress] * 256; IF r.Fetch[pos - 2] # ' THEN ERROR BadAddress; idAddr _ idAddr + Hex2[r: r, pos: pos - 4 ! BadHex => ERROR BadAddress]; RETURN[LowWord[idAddr]]; }; pos _ pos - 1; ENDLOOP; }; HexChar: PROC [ch: CHAR] RETURNS [BOOL] = INLINE { RETURN [ch IN ['0..'9] OR ch IN ['A..'F]]; }; ScanFile: PROC [map, out: IO.STREAM] = { id: Rope.ROPE; idAddr: INT; l: Rope.ROPE; len: INT; semiColon: INT; address: INT; offset: INT; idPos: INT; fileName: Rope.ROPE; DO { l _ IO.GetLineRope[map]; len _ l.Length[]; IF map.EndOf[] THEN EXIT; IF len > 0 AND l.Fetch[0] = '; THEN { IF l.Find[";File "] = 0 THEN fileName _ Rope.Substr[base: l, start: 6, len: len - 6]; }; IF len < 10 THEN GOTO TryNextLine; IF l.Find["[0000]"] # 0 THEN GOTO TryNextLine; IF fileName # NIL THEN { idAddr _ Convert.CardFromRope[Rope.Substr[base: l, start: 6, len: 4], 16 ! Convert.Error => { out.PutF["Can't find address for file %g\n", IO.rope[fileName]]; idAddr _ 0; CONTINUE}]; EnterFile[id: fileName, idAddr: Convert.CardFromRope[Rope.Substr[base: l, start: 6, len: 4], 16]]; fileName _ NIL; }; IF l.Find["_"] = -1 THEN GOTO TryNextLine; semiColon _ l.Find[";"]; IF semiColon = -1 THEN GOTO TryNextLine; idPos _ l.Find["CALL", semiColon]; IF idPos # -1 THEN { IF l.Fetch[idPos + 4] # ' AND l.Fetch[idPos + 4] # '\t THEN GOTO TryNextLine; IF l.Fetch[idPos + 5] # '_ THEN GOTO TryNextLine; IF l.Find[": E8 ", 10] # 10 THEN GOTO TryNextLine; address _ Hex4[r: l, pos: 6 ! BadHex => GOTO TryNextLine]; offset _ Hex2[r: l, pos: 16 ! BadHex => GOTO TryNextLine]; IF l.Fetch[18] # ' THEN GOTO TryNextLine; offset _ offset + (Hex2[r: l, pos: 19 ! BadHex => GOTO TryNextLine] * 256); idAddr _ address + offset + 3; idAddr _ LowWord[idAddr]; id _ GetID[r: l, pos: idPos + 6 ! BadID => GOTO TryNextLine]; EnterProc[id: id, idAddr: idAddr]; GOTO TryNextLine; }; idPos _ l.Find["MOV", semiColon]; IF idPos# -1 THEN { { IF l.Find[": 8B ", 10] = 10 THEN { idPos _ l.Find[",_", semiColon + 3]; IF idPos # -1 THEN { id _ GetID[r: l, pos: idPos + 2 ! BadID => GOTO TryNextLine]; GOTO FoundID; }; idPos _ l.Find[",WORD PTR _", semiColon + 3]; IF idPos # -1 THEN { id _ GetID[r: l, pos: idPos + 11 ! BadID => GOTO TryNextLine]; GOTO FoundID; }; }; IF l.Find[": BB ", 10] = 10 OR l.Find[": B9 ", 10] = 10 THEN { idPos _ l.Find[",OFFSET _", semiColon + 3]; IF idPos # -1 THEN { id _ GetID[r: l, pos: idPos + 9 ! BadID => GOTO TryNextLine]; GOTO FoundID; }; }; GOTO TryNextLine; EXITS FoundID => NULL; }; idAddr _ GetVarAddress[r: l, pos: semiColon - 1 ! BadAddress => GOTO TryNextLine]; EnterVar[id: id, idAddr: idAddr]; GOTO TryNextLine; }; IF l.Find["LEA", semiColon] = semiColon + 1 THEN { idPos _ l.Find[",_", semiColon]; IF idPos = -1 THEN GOTO TryNextLine; id _ GetID[r: l, pos: idPos + 2 ! BadID => GOTO TryNextLine]; idAddr _ GetVarAddress[r: l, pos: semiColon - 1 ! BadAddress => GOTO TryNextLine]; EnterVar[id: id, idAddr: idAddr]; GOTO TryNextLine; }; IF l.Find["INC", semiColon] = semiColon + 1 THEN { idPos _ l.Find["_", semiColon]; IF idPos = -1 THEN GOTO TryNextLine; id _ GetID[r: l, pos: idPos + 1 ! BadID => GOTO TryNextLine]; idAddr _ GetVarAddress[r: l, pos: semiColon - 1 ! BadAddress => GOTO TryNextLine]; EnterVar[id: id, idAddr: idAddr]; GOTO TryNextLine; }; EXITS TryNextLine => NULL; }; ENDLOOP; }; TryForPublics: PROC [map, out: IO.STREAM] = { id: Rope.ROPE; idAddr: INT; l: Rope.ROPE; len: INT; addrPos: INT; DO { l _ IO.GetLineRope[map]; len _ l.Length[]; IF map.EndOf[] THEN EXIT; IF len > 0 AND l.Fetch[0] = '; THEN EXIT; IF len < 10 THEN GOTO TryNextLine; addrPos _ l.Find["Offset = "]; IF addrPos > 0 THEN { idAddr _ Hex4[r: l, pos: addrPos + 9 ! BadHex => GOTO TryNextLine]; IF l.Fetch[0] = '_ THEN id _ GetMidID[r: l, pos: 1 ! BadID => GOTO TryNextLine] ELSE id _ GetID[r: l, pos: 0 ! BadID => GOTO TryNextLine]; IF l.Find["C_CODE"] # -1 THEN EnterProc[id: id, idAddr: idAddr] ELSE EnterVar[id: id, idAddr: idAddr]; }; EXITS TryNextLine => NULL; }; ENDLOOP; }; nameTable: RedBlackTree.Table _ NIL; addrTable: RedBlackTree.Table _ NIL; fileTable: RedBlackTree.Table _ NIL; CompareSTItemsByName: RedBlackTree.Compare = { keyItem: LarkPrograms.STItem _ NARROW[k]; dataItem: LarkPrograms.STItem _ NARROW[data]; RETURN[Rope.Compare[s1: keyItem.id, s2: dataItem.id, case: FALSE]]; }; CompareSTItemsByAddr: RedBlackTree.Compare = { keyItem: LarkPrograms.STItem _ NARROW[k]; dataItem: LarkPrograms.STItem _ NARROW[data]; RETURN[Basics.CompareInt[keyItem.addr, dataItem.addr]]; }; FileDataObject: TYPE = RECORD [ id: Rope.ROPE _ NIL, addr: INT _ 0, size: INT _ 0, codeAddr: INT _ 0, codeSize: INT _ 0, dataAddr: INT _ 0, dataSize: INT _ 0 ]; FileData: TYPE = REF FileDataObject; GetKey: RedBlackTree.GetKey = {RETURN[data]; }; CompareFileItemsByAddr: RedBlackTree.Compare = { keyFileData: FileData _ NARROW[k]; dataFileData: FileData _ NARROW[data]; RETURN[Basics.CompareInt[keyFileData.addr, dataFileData.addr]]; }; EnterProc: PROC [id: Rope.ROPE, idAddr: INT] = { item: LarkPrograms.STItem _ NEW[LarkPrograms.STObject _ [id: id, addr: idAddr, type: procedure]]; nameTable.Insert[item, item ! RedBlackTree.DuplicateKey => CONTINUE]; }; EnterVar: PROC [id: Rope.ROPE, idAddr: INT] = { item: LarkPrograms.STItem _ NEW[LarkPrograms.STObject _ [id: id, addr: idAddr, type: variable]]; nameTable.Insert[item, item ! RedBlackTree.DuplicateKey => CONTINUE]; }; EnterFile: PROC [id: Rope.ROPE, idAddr: INT] = { item: FileData _ NEW[FileDataObject _ [id: id, addr: idAddr]]; fileTable.Insert[item, item ! RedBlackTree.DuplicateKey => CONTINUE]; }; MapParse: Commander.CommandProc = { map: IO.STREAM; extension: INT; scanFileSymbols: INT; argv: CommandTool.ArgumentVector; out: IO.STREAM _ cmd.out; PrintSTItem: PROC [a: REF ANY] RETURNS [BOOL _ FALSE] = { c: CHAR; item: LarkPrograms.STItem _ NARROW[a]; c _ SELECT item.type FROM procedure => 'P, variable => 'V, ENDCASE => ERROR; out.PutF["%c %04x %g\n", IO.char[c], IO.int[item.addr], IO.rope[item.id]]; }; EnterIntoAddrTable: PROC [a: REF ANY] RETURNS [BOOL _ FALSE] = { addrTable.Insert[a, a ! RedBlackTree.DuplicateKey => { out.PutF["Duplicate:\n\t"]; [] _ PrintSTItem[a]; CONTINUE; }]; }; argv _ CommandTool.Parse[cmd ! CommandTool.Failed => {msg _ errorMsg; CONTINUE; }]; IF argv = NIL THEN RETURN[$Failure, msg]; IF argv.argc # 2 THEN { out.PutF["Usage: MapParse MapFileName (without extension)\n"]; RETURN; }; extension _ Rope.Find[s1: argv[1], s2: ".map", case: FALSE]; IF extension # -1 THEN argv[1] _ Rope.Substr[base: argv[1], start: 0, len: extension]; map _ FS.StreamOpen[fileName: Rope.Cat[argv[1], ".map"] ! FS.Error => { out.PutRope[error.explanation]; out.PutRope["\n"]; GOTO Die; }]; nameTable _ RedBlackTree.Create[getKey: GetKey, compare: CompareSTItemsByName]; addrTable _ RedBlackTree.Create[getKey: GetKey, compare: CompareSTItemsByAddr]; fileTable _ RedBlackTree.Create[getKey: GetKey, compare: CompareFileItemsByAddr]; ScanFile[map: map, out: out]; scanFileSymbols _ nameTable.Size[]; map.SetIndex[0]; TryForPublics[map: map, out: out]; out.PutF["TryForPublics found %d more symbols\n", IO.card[nameTable.Size[] - scanFileSymbols]]; nameTable.EnumerateIncreasing[procToApply: EnterIntoAddrTable]; map.Close[]; out.PutF["\nIdentifiers: (%d items)\n", IO.int[nameTable.Size[]]]; { symsFileName: Rope.ROPE _ Rope.Cat[argv[1], ".syms"]; symsFile: IO.STREAM; PrintFileItem: PROC [a: REF ANY] RETURNS [BOOL _ FALSE] = { item: LarkPrograms.STItem _ NARROW[a]; symsFile.PutF["%04x %g\n", IO.int[item.addr], IO.rope[item.id]]; }; out.PutF["Writing symbol file: %g.\n", IO.rope[symsFileName]]; symsFile _ FS.StreamOpen[fileName: symsFileName, accessOptions: $create ! FS.Error => { out.PutRope[error.explanation]; out.PutRope["\n"]; GOTO Die; }]; WriteSortedFile[errors: out, tabFile: symsFile, byName: TRUE]; WriteSortedFile[errors: out, tabFile: symsFile, byName: FALSE]; symsFile.Close[]; symsFileName _ Rope.Cat[argv[1], ".files"]; out.PutF["Writing files file: %g.\n", IO.rope[symsFileName]]; symsFile _ FS.StreamOpen[fileName: symsFileName, accessOptions: $create ! FS.Error => { out.PutRope[error.explanation]; out.PutRope["\n"]; GOTO Die; }]; PrintFileData[tab: fileTable, syms: symsFile, log: out]; symsFile.Close[]; EXITS Die => NULL; }; out.PutF["Releasing RedBlackTrees.\n"]; IF nameTable # NIL THEN { nameTable.DestroyTable[]; nameTable _ NIL; }; IF addrTable # NIL THEN { addrTable.DestroyTable[]; addrTable _ NIL; }; IF fileTable # NIL THEN { fileTable.DestroyTable[]; fileTable _ NIL; }; EXITS Die => NULL; }; PrintFileData: PROC [tab: RedBlackTree.Table, syms, log: IO.STREAM] = { fileList: LIST OF REF ANY; previous: FileData _ NIL; totalCode: INT _ 0; totalData: INT _ 0; GetSizes: PROC [a: REF ANY] RETURNS [BOOL _ FALSE] = { item: FileData _ NARROW[a]; IF previous # NIL THEN previous.size _ item.addr - previous.addr; previous _ item; }; CollectFileData: PROC [a: REF ANY] RETURNS [BOOL _ FALSE] = { item: FileData _ NARROW[a]; FOR l: LIST OF REF ANY _ fileList, l.rest WHILE l # NIL DO file: FileData _ NARROW[l.first]; IF Rope.Equal[item.id, file.id, FALSE] THEN { file.dataAddr _ item.addr; file.dataSize _ item.size; RETURN; }; ENDLOOP; item.codeAddr _ item.addr; item.codeSize _ item.size; fileList _ CONS[item, fileList]; }; tab.EnumerateIncreasing[procToApply: GetSizes]; tab.EnumerateIncreasing[procToApply: CollectFileData]; fileList _ List.Reverse[fileList]; syms.PutF["Module code size data size total\n"]; FOR l: LIST OF REF ANY _ fileList, l.rest WHILE l # NIL DO item: FileData _ NARROW[l.first]; totalCode _ totalCode + item.codeSize; totalData _ totalData + item.dataSize; syms.PutF["%-20g %04x %5d", IO.rope[item.id], IO.card[item.codeAddr], IO.card[item.codeSize]]; syms.PutF[" %04x %5d %5d\n", IO.card[item.dataAddr], IO.card[item.dataSize], IO.card[item.codeSize + item.dataSize]]; ENDLOOP; syms.PutF["Totals: Code %d, Data %d, Everything %d\n", IO.card[totalCode], IO.card[totalData], IO.card[totalCode + totalData]]; }; WriteSortedFile: PROC [errors: IO.STREAM, tabFile: IO.STREAM, byName: BOOL] = { tab: RedBlackTree.Table _ IF byName THEN nameTable ELSE addrTable; PrintSTItem: PROC [a: REF ANY] RETURNS [BOOL _ FALSE] = { c: CHAR; item: LarkPrograms.STItem _ NARROW[a]; c _ SELECT item.type FROM procedure => 'P, variable => 'V, ENDCASE => ERROR; tabFile.PutF["%c %04x %g\n", IO.char[c], IO.int[item.addr], IO.rope[item.id]]; }; tabFile.PutF[IF byName THEN "Names %d\n" ELSE "Addresses %d\n", IO.int[tab.Size[]]]; tab.EnumerateIncreasing[procToApply: PrintSTItem]; }; Commander.Register[key: "MapParse", proc: MapParse, doc: "MapParse usage: MapParse Lark for Lark.map"]; END. November 15, 1982 9:51 pm, L. Stewart, Created November 16, 1982 8:46 am, L. Stewart, added RedBlackTree stuff November 16, 1982 2:39 pm, L. Stewart, integration with Teleload November 19, 1982 4:59 pm, L. Stewart December 6, 1982 4:41 pm, L. Stewart, Cedar 3.5 December 18, 1982 4:38 pm, L. Stewart, Stand alone service May 2, 1983 12:47 pm, L. Stewart, LarkPrograms May 14, 1985 4:32:38 pm PDT, L. Stewart, Cedar 5, December ΠMapParseImpl.mesa Copyright c 1985 by Xerox Corporation. All rights reserved. Last modified: L. Stewart, December 22, 1983 1:56 pm A program to build up a symbol table, given a C map file Swinehart, April 3, 1987 5:45:23 pm PST Convert from CARDINAL to INTEGER (both kept in an INT) address is the last two bytes before the "\t;" An interestng line must start with a '[ and must contain a ;, a _, and "CALL" or "MOV" first 6 characters must be "[0000]" The line must contain a C identifier The line must be a CALL or MOV or LEA instruction address = four chars starting at pos 6 id token starting at idPos + 6 id follows the ",_" or ",WORD PTR _" id follows the ",OFFSET _" address is the last two bytes before the "\t;" id follows the ",_" address is the last two bytes before the "\t;" id follows the "_" address is the last two bytes before the "\t;" There must be an "Offset = " Here is the symbol table part out.PutF["proc: %04x %g\n", IO.int[idAddr], IO.rope[id]]; out.PutF["var: %04x %g\n", IO.int[idAddr], IO.rope[id]]; appears to be no way to get the other one, monitors locked out.PutF["\t"]; [] _ PrintSTItem[addrTable.Lookup[a]]; Swinehart, May 14, 1985 4:32:13 pm PDT Cedar 6.0 changes to: DIRECTORY (OrderedSymbolTableRef => RedBlackTree throughout), MapParseImpl, nameTable, addrTable, fileTable, CompareSTItemsByName, CompareSTItemsByAddr, CompareFileItemsByAddr, EnterProc, EnterVar, EnterFile, EnterIntoAddrTable (local of MapParse), MapParse, PrintFileData ΚΝ˜šœ™Icodešœ Οmœ1™<—Jšœ4™4šœ8™8K™'—J˜šΟk ˜ Jšœžœ˜#Jšœ žœ˜(Jšœ žœ!˜2J˜$Jšžœžœ˜Jšžœ˜J˜ Jšœžœ ˜J˜ Jšœ˜J˜—šœžœž˜Jšžœ(žœžœ˜S—Jšž˜J˜Jšœžœžœ˜Jšœžœžœ˜Jšœ žœžœ˜J˜š Οnœžœ žœžœžœžœ˜:J˜GJ˜GJ˜J˜—š Ÿœžœ žœžœžœžœ˜:J˜=J˜J˜—š Ÿœžœ žœžœžœžœ˜5Jšœžœ˜šžœžœž˜Jšœ žœ ˜Jšœžœ žœ˜(Jšžœžœ˜—J˜J˜—JšŸœžœžœžœžœžœžœžœ˜JJšŸœžœžœžœžœžœžœžœ žœžœ˜\J˜J˜š Ÿœžœžœžœžœ˜(Jšœžœ˜ Jšžœ˜ J˜J˜—Jšœ7™7š Ÿœžœ žœžœžœžœ˜Jšžœ ˜ J˜—J˜—šžœžœžœ˜@Jšœ™J˜+šžœ žœ˜Jšœ+žœ˜=Jšžœ ˜ J˜—J˜—Jšžœ ˜Jšž˜Jšœ žœ˜J˜Jšœ.™.Jšœ@žœ˜RJ˜!Jšžœ ˜J˜—šžœ*žœ˜2Jšœ™J˜ Jšžœ žœžœ ˜$Jšœ+žœ˜=Jšœ.™.Jšœ@žœ˜RJ˜!Jšžœ ˜J˜—šžœ*žœ˜2Jšœ™J˜Jšžœ žœžœ ˜$Jšœ+žœ˜=Jšœ.™.Jšœ@žœ˜RJ˜!Jšžœ ˜J˜—Jšž˜Jšœžœ˜J˜Jšžœ˜—J˜J˜—šŸ œžœ žœžœ˜-Jšœ žœ˜Jšœžœ˜ Jšœžœ˜ Jšœžœ˜ Jšœ žœ˜ šž˜J˜Jšœžœ˜J˜Jšžœ žœžœ˜Jšžœ žœžœžœ˜)Jšžœ žœžœ ˜"J™Jšœ˜šžœ žœ˜Jšœ1žœ˜CJšžœžœ'žœ ˜OJšžœ$žœ˜:Jšžœžœ"˜?Jšžœ"˜&J˜—Jšž˜Jšœžœ˜J˜Jšžœ˜—J˜J˜—Jšœ™J˜Jšœ žœ˜$Jšœ žœ˜$Jšœ žœ˜$J˜˜.Jšœžœ˜)Jšœ žœ˜-Jšžœ5žœ˜CJ˜J˜—˜.Jšœžœ˜)Jšœ žœ˜-Jšžœ1˜7J˜—J˜šœžœžœ˜Jšœ žœžœ˜Jšœžœ˜Jšœžœ˜Jšœ žœ˜Jšœ žœ˜Jšœ žœ˜Jšœ žœ˜J˜—Jšœ žœžœ˜$J˜Jšœžœ ˜/J˜˜0Jšœžœ˜"Jšœžœ˜&Jšžœ9˜?J˜—J˜šŸ œžœ žœ žœ˜0JšœžœB˜aJšœ;žœ˜EJšœ:™:J˜J˜—šŸœžœ žœ žœ˜/JšœžœA˜`Jšœ;žœ˜EJšœ:™:J˜—J˜šŸ œžœ žœ žœ˜0Jšœžœ*˜>Jšœ;žœ˜EJ˜J˜—˜#Jšœžœžœ˜Jšœ žœ˜Jšœžœ˜Jšœ!˜!Jšœžœžœ ˜šŸ œžœžœžœžœžœžœ˜9Jšœžœ˜Jšœžœ˜&šœžœ ž˜J˜J˜Jšžœžœ˜—Jšœžœ žœžœ˜KJ˜J˜—šŸœžœžœžœžœžœžœ˜@˜6J˜J˜Jšœ:™:Jšœ™Jšœ&™&Jšžœ˜ J˜—J˜—JšœGžœ˜TJšžœžœžœžœ˜)šžœžœ˜J˜>Jšžœ˜J˜—Jšœ5žœ˜šœ žœ=žœ ˜WJšœ˜Jšœ˜Jšžœ˜ J˜J˜—Jšœ8žœ˜>Jšœ8žœ˜?J˜Jšœ+˜+Jšœ&žœ˜=šœ žœ=žœ ˜WJšœ˜Jšœ˜Jšžœ˜ J˜—J˜8J˜J˜Jšž˜Jšœžœ˜ J˜J˜J˜'šžœ žœžœ˜J˜Jšœ žœ˜J˜—šžœ žœžœ˜J˜Jšœ žœ˜J˜—šžœ žœžœ˜Jšœ˜Jšœ žœ˜J˜—Jšž˜Jšœžœ˜ J˜J˜—šŸ œžœ&žœžœ˜GJš œ žœžœžœžœ˜Jšœžœ˜Jšœ žœ˜Jšœ žœ˜šŸœžœžœžœžœžœžœ˜6Jšœžœ˜Jšžœ žœžœ+˜AJšœ˜J˜—šŸœžœžœžœžœžœžœ˜=Jšœžœ˜šžœžœžœžœžœžœžœž˜:Jšœžœ ˜!šžœžœžœ˜-Jšœ˜Jšœ˜Jšžœ˜J˜—Jšžœ˜—Jšœ˜Jšœ˜Jšœ žœ˜ J˜—Jšœ/˜/J˜6J˜"JšœD˜DJ˜šžœžœžœžœžœžœžœž˜:Jšœžœ ˜!Jšœ&˜&Jšœ&˜&Jšœžœžœžœ˜_Jšœ žœžœžœ&˜xJšžœ˜—Jšœ7žœžœžœ˜J˜—J˜JšŸœžœ žœžœ žœžœ žœ˜O˜šœžœžœ ˜2Jšžœ ˜J˜—šŸ œžœžœžœžœžœžœ˜9Jšœžœ˜Jšœžœ˜&šœžœ ž˜J˜J˜Jšžœžœ˜—Jšœžœ žœžœ˜OJ˜—Jš œ žœžœžœžœ˜TJ˜2J˜J˜—Jšœg˜gJ˜Jšžœ˜Jšœžœ˜.Jšœžœ#˜?Jšœžœ$˜@Jšœžœ ˜%Jšœžœ˜/Jšœžœ˜:Jšœžœ˜.JšœžœΟr ˜:J˜™&K™ Kšœ   œ3 §œ ™œ—K™—…—3XJυ