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], OrderedSymbolTableRef, Rope; MapParseImpl: CEDAR PROGRAM IMPORTS Basics, Commander, CommandTool, Convert, FS, IO, List, OrderedSymbolTableRef, 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: OrderedSymbolTableRef.Table _ NIL; addrTable: OrderedSymbolTableRef.Table _ NIL; fileTable: OrderedSymbolTableRef.Table _ NIL; CompareSTItemsByName: OrderedSymbolTableRef.CompareProc = { x: LarkPrograms.STItem _ NARROW[r1]; y: LarkPrograms.STItem _ NARROW[r2]; RETURN[Rope.Compare[s1: x.id, s2: y.id, case: FALSE]]; }; CompareSTItemsByAddr: OrderedSymbolTableRef.CompareProc = { x: LarkPrograms.STItem _ NARROW[r1]; y: LarkPrograms.STItem _ NARROW[r2]; RETURN[Basics.CompareINT[x.addr, y.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; CompareFileItemsByAddr: OrderedSymbolTableRef.CompareProc = { x: FileData _ NARROW[r1]; y: FileData _ NARROW[r2]; RETURN[Basics.CompareINT[x.addr, y.addr]]; }; EnterProc: PROC [id: Rope.ROPE, idAddr: INT] = { item: LarkPrograms.STItem _ NEW[LarkPrograms.STObject _ [id: id, addr: idAddr, type: procedure]]; nameTable.Insert[item ! OrderedSymbolTableRef.DuplicateKey => CONTINUE]; }; EnterVar: PROC [id: Rope.ROPE, idAddr: INT] = { item: LarkPrograms.STItem _ NEW[LarkPrograms.STObject _ [id: id, addr: idAddr, type: variable]]; nameTable.Insert[item ! OrderedSymbolTableRef.DuplicateKey => CONTINUE]; }; EnterFile: PROC [id: Rope.ROPE, idAddr: INT] = { item: FileData _ NEW[FileDataObject _ [id: id, addr: idAddr]]; fileTable.Insert[item ! OrderedSymbolTableRef.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 ! OrderedSymbolTableRef.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 _ OrderedSymbolTableRef.CreateTable[compareProc: CompareSTItemsByName]; addrTable _ OrderedSymbolTableRef.CreateTable[compareProc: CompareSTItemsByAddr]; fileTable _ OrderedSymbolTableRef.CreateTable[compareProc: 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: OrderedSymbolTableRef.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: OrderedSymbolTableRef.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 December 22, 1983 1:56 pm, L. Stewart, Cedar 5 MapParseImpl.mesa Last modified: L. Stewart, December 22, 1983 1:56 pm A program to build up a symbol table, given a C map file 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]]; �ʆ˜Jšœ™Jšœ4™4Jšœ8™8J˜šÏk ˜ Jšœ�œ˜#Jšœ �œ˜(Jšœ �œ!˜2J˜$Jš�œ�œ˜Jš�œ˜J˜ Jšœ�œ ˜J˜Jšœ˜J˜—šœ�œ�˜Jš�œ(�œ�œ%˜\—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š�œ(�œ˜6J˜J˜—˜;Jšœ�œ˜$Jšœ�œ˜$Jš�œ$˜*J˜—J˜šœ�œ�œ˜Jšœ �œ�œ˜Jšœ�œ˜Jšœ�œ˜Jšœ �œ˜Jšœ �œ˜Jšœ �œ˜Jšœ �œ˜J˜—Jšœ �œ�œ˜$J˜˜=Jšœ�œ˜Jšœ�œ˜Jš�œ$˜*J˜—J˜šž œ�œ �œ �œ˜0Jšœ�œB˜aJšœ>�œ˜HJšœ:™:J˜J˜—šžœ�œ �œ �œ˜/Jšœ�œA˜`Jšœ>�œ˜HJšœ:™:J˜—J˜šž œ�œ �œ �œ˜0Jšœ�œ*˜>Jšœ>�œ˜HJ˜J˜—˜#Jšœ�œ�œ˜Jšœ �œ˜Jšœ�œ˜Jšœ!˜!Jšœ�œ�œ ˜šž œ�œ�œ�œ�œ�œ�œ˜9Jšœ�œ˜Jšœ�œ˜&šœ�œ �˜J˜J˜Jš�œ�œ˜—Jšœ�œ �œ�œ˜KJ˜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˜—šž œ�œ/�œ�œ˜PJš œ �œ�œ�œ�œ˜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˜šœ#�œ�œ ˜;Jš�œ ˜J˜—šž œ�œ�œ�œ�œ�œ�œ˜9Jšœ�œ˜Jšœ�œ˜&šœ�œ �˜J˜J˜Jš�œ�œ˜—Jšœ�œ �œ�œ˜OJ˜—Jš œ �œ�œ�œ�œ˜TJ˜2J˜J˜—Jšœg˜gJ˜Jš�œ˜Jšœ�œ˜.Jšœ�œ#˜?Jšœ�œ$˜@Jšœ�œ ˜%Jšœ�œ˜/Jšœ�œ˜:Jšœ�œ˜.Jšœ�œ˜.—…—34HÖ