DIRECTORY ConvertUnsafe USING [AppendRope, ToRope], Directory USING [GetNext, Lookup, Error, ignore], IO USING [char, GetOutputStreamRope, GetToken, NUL, IDProc, Put, PutChar, PutRope, rope, RIS, ROS, STREAM], MessageWindow USING [Append, Clear], Process USING [Detach], Rope USING [Cat, Concat, Equal, IsEmpty, Fetch, Find, Flatten, Length, Lower, ROPE, Substr, Text, ToRefText, Upper], RopeInline USING [InlineFlatten], Spell USING [AbortProc, ConfirmProc, defaultModes, extensionList, GeneratorFromProcs, GetMatchingList, GetTheOne, InformProc, IsAPattern, Modes, ROPE, SpellingGenerator, SpellingList], SpellExtras USING [], UnsafeStorage USING [GetSystemUZone], UserProfile USING [CallWhenProfileChanges, ProfileChangedProc, Boolean], WindowManager USING [WaitCursor, UnWaitCursor] ; FileSpellImpl: CEDAR MONITOR IMPORTS ConvertUnsafe, Directory, IO, MessageWindow, Process, Rope, RopeInline, Spell, UnsafeStorage, UserProfile, WindowManager EXPORTS Spell, SpellExtras SHARES Rope -- to be able to say text.length = BEGIN OPEN Spell; extensionList: PUBLIC Spell.SpellingList _ LIST["mesa", "cm", "config", "commands", "profile", "df", "doc", "log", "style", "abbreviations", "press", "bcd", "tioga", "mail"]; AddExtension: PUBLIC ENTRY PROC [ext: ROPE] = { ENABLE UNWIND => NULL; FOR l: Spell.SpellingList _ extensionList, l.rest UNTIL l = NIL DO IF Rope.Equal[l.first, ext, FALSE] THEN EXIT; REPEAT FINISHED => extensionList _ CONS[ext, extensionList]; ENDLOOP; }; assumeFirstCharCorrect: BOOL; SetUp: UserProfile.ProfileChangedProc = { assumeFirstCharCorrect _ UserProfile.Boolean["Spell.assumeFirstCharCorrect" , FALSE]; }; GetTheFile: PUBLIC PROC [ unknown: ROPE, defaultExt: ROPE _ NIL, abort: AbortProc _ NIL, confirm: ConfirmProc _ NIL, inform: InformProc _ NIL, modes: Modes _ NIL ] RETURNS [correct: ROPE] = { ENABLE UNWIND => NULL; root, ext, msg: ROPE; unwaitCursor, releaseGenerator: BOOLEAN; IF Rope.IsEmpty[unknown] THEN RETURN[NIL]; IF modes = NIL THEN modes _ Spell.defaultModes; [root, ext, correct] _ SetUpGenerator[unknown: unknown, defaultExt: defaultExt, abort: abort, confirm: confirm, inform: inform, modes: modes]; IF correct # NIL THEN {ReleaseGenerator[]; RETURN[correct]}; -- only mistake was extension. releaseGenerator _ TRUE; -- the generator belongs to this call. it must be released. msg _ Rope.Concat["Trying for spelling correction on ", root]; WindowManager.WaitCursor[]; IF ext # NIL THEN msg _ Rope.Cat[msg, ".", ext]; MessageWindow.Append[message: msg, clearFirst: TRUE]; unwaitCursor _ TRUE; { ENABLE UNWIND => { MessageWindow.Clear[]; IF unwaitCursor THEN WindowManager.UnWaitCursor[]; IF releaseGenerator THEN ReleaseGenerator[]; }; Inform: Spell.InformProc -- [msg: ROPE] -- = { IF releaseGenerator THEN {ReleaseGenerator[]; releaseGenerator _ FALSE}; -- see comment in Confirm below. IF inform = NIL THEN RETURN; IF ext # NIL AND Rope.Find[s1: unknown, s2: "."] # -1 THEN msg _ Rope.Cat[msg, ".", ext]; -- only output extension if extension in original unknown, as opposed to being obtained from defaultExt. inform[msg] }; Confirm: Spell.ConfirmProc -- [msg: ROPE, timeout: INT, defaultConfirm: BOOL] RETURNS[yes: BOOLEAN] -- = { i: INT = Rope.Find[msg, " -> "]; IF releaseGenerator THEN {ReleaseGenerator[]; releaseGenerator _ FALSE}; -- in case user simply ignores this and does something else, spelling corrector is not hung. Need to set releaseGenerator to FALSE so that if he comes back to this later and confirms, when another correction has started, won't mistakenly release the generator again, thereby allowing a third correction to disrupt the second one still in progress. IF i = -1 THEN ERROR; IF Rope.Find[s1: msg, s2: " ", pos1: i + 4] # -1 THEN RETURN[FALSE]; -- more than one candidate. don't bother confirming. CHECK THIS IF confirm = NIL THEN RETURN[FALSE]; unwaitCursor _ FALSE; WindowManager.UnWaitCursor[]; IF ext # NIL AND Rope.Find[s1: unknown, s2: "."] # -1 THEN msg _ Rope.Cat[msg, ".", ext]; -- only output extension if extension in original unknown, as opposed to being obtained from defaultExt. RETURN[confirm[msg, timeout, defaultConfirm]]; }; -- end of confirm IF IsAPattern[unknown] THEN { l: LIST OF ROPE = Spell.GetMatchingList[pattern: root, generator: fileGenerator, abort: abort, confirm: Confirm, inform: Inform, modes: modes]; IF l # NIL AND l.rest = NIL THEN correct _ l.first ELSE correct _ NIL; } ELSE { correct _ GetTheOne[unknown: root, generator: fileGenerator, abort: abort, confirm: IF confirm = NIL THEN NIL ELSE Confirm, inform: IF inform = NIL THEN NIL ELSE Inform, modes: modes]; IF correct = NIL AND fileGenState.stopAfter # LAST[CHARACTER] AND NOT assumeFirstCharCorrect AND releaseGenerator THEN -- no candidates found in corresponding bucket, or else, one was found and user rejected confirmation. now search entire space. TRUSTED { fileGenState.stopAfter _ LAST[CHARACTER]; fileGenState.nextName.length _ 0; fileGenState.currentName.length _ 0; correct _ GetTheOne[unknown: root, generator: fileGenerator, abort: abort, confirm: IF confirm = NIL THEN NIL ELSE Confirm, inform: IF inform = NIL THEN NIL ELSE Inform, modes: modes]; }; }; }; -- end of inner block containing definitions of inform, confirm, etc. IF correct # NIL AND ext # NIL THEN correct _ Rope.Cat[correct, ".", ext]; MessageWindow.Clear[]; IF unwaitCursor THEN WindowManager.UnWaitCursor[]; IF releaseGenerator THEN ReleaseGenerator[]; }; GetFileExtension: PUBLIC PROC [ unknown: Rope.ROPE, spellingList: Spell.SpellingList _ Spell.extensionList, abort: AbortProc _ NIL, confirm: ConfirmProc _ NIL, inform: InformProc _ NIL, modes: Modes _ NIL ] RETURNS [correct: Rope.ROPE] = { len, len1: INT; ext1: ROPE; len _ len1 _ Rope.Length[unknown]; IF len1 > 0 THEN WHILE Rope.Fetch[unknown, len1 - 1] = '$ DO len1 _ len1 - 1; ENDLOOP; IF len1 # len THEN ext1 _ Rope.Substr[base: unknown, len: len1] ELSE ext1 _ unknown; FOR l: SpellingList _ spellingList, l.rest UNTIL l = NIL DO IF Rope.Equal[l.first, ext1, FALSE] THEN RETURN[unknown]; ENDLOOP; correct _ GetTheOne[unknown: ext1, spellingList: spellingList, abort: abort, confirm: confirm, modes: modes]; -- dont print message. want to print it once for entire correction (which may fail). IF correct # NIL THEN { IF len # len1 THEN correct _ Rope.Concat[correct, Rope.Substr[base: unknown, start: len1]]; -- put $'s back }; }; GetMatchingFileList: PUBLIC PROCEDURE [ unknown: ROPE, defaultExt: ROPE _ NIL, abort: AbortProc _ NIL, confirm: ConfirmProc _ NIL, inform: InformProc _ NIL, modes: Modes _ NIL] RETURNS [files: LIST OF ROPE] = { ENABLE UNWIND => NULL; root, ext, correct, msg: ROPE; unwaitCursor, releaseGenerator: BOOLEAN; IF Rope.IsEmpty[unknown] THEN RETURN[NIL]; IF modes = NIL THEN modes _ Spell.defaultModes; [root, ext, correct] _ SetUpGenerator[unknown: unknown, defaultExt: defaultExt, abort: abort, confirm: confirm, inform: inform, modes: modes]; IF correct # NIL THEN {ReleaseGenerator[]; RETURN[LIST[correct]]}; -- only mistake was extension. releaseGenerator _ TRUE; -- the generator belongs to this call. it must be released. IF inform # NIL THEN { msg _ Rope.Concat["Trying for pattern completion on ", root]; IF ext # NIL THEN msg _ Rope.Cat[msg, ".", ext]; MessageWindow.Append[message: msg, clearFirst: TRUE]; WindowManager.WaitCursor[]; unwaitCursor _ TRUE; }; { ENABLE UNWIND => { MessageWindow.Clear[]; IF unwaitCursor THEN WindowManager.UnWaitCursor[]; IF releaseGenerator THEN ReleaseGenerator[]; }; Inform: Spell.InformProc -- [msg: ROPE] -- = { IF releaseGenerator THEN {ReleaseGenerator[]; releaseGenerator _ FALSE}; -- see comments in GetTheOne. IF inform = NIL THEN RETURN; IF ext # NIL AND Rope.Find[s1: unknown, s2: "."] # -1 THEN msg _ AddExt[msg]; -- only output extension if extension in original unknown, as opposed to being obtained from defaultExt. inform[msg] }; Confirm: Spell.ConfirmProc -- [msg: ROPE, timeout: INT, defaultConfirm: BOOL] RETURNS[yes: BOOLEAN] -- = { IF releaseGenerator THEN {ReleaseGenerator[]; releaseGenerator _ FALSE}; -- see comments in GetTheOne. IF confirm = NIL THEN RETURN[FALSE]; unwaitCursor _ FALSE; WindowManager.UnWaitCursor[]; IF ext # NIL AND Rope.Find[s1: unknown, s2: "."] # -1 THEN msg _ AddExt[msg]; -- only output extension if extension in original unknown, as opposed to being obtained from defaultExt. RETURN[confirm[msg, timeout, defaultConfirm]]; }; AddExt: PROC [r: ROPE] RETURNS [ROPE] = { OPEN IO; stream1, stream2: STREAM; token: ROPE; stream1 _ RIS[r]; stream2 _ ROS[]; UNTIL Rope.Length[token _ stream1.GetToken[IO.IDProc]] = 0 DO stream2.PutRope[token]; IF NOT Rope.Equal[token, "->"] THEN stream2.Put[char['.], rope[ext]]; stream2.PutChar[' ]; ENDLOOP; RETURN[stream2.GetOutputStreamRope[]]; }; IF NOT IsAPattern[unknown] THEN { x: ROPE = GetTheOne[unknown: root, generator: fileGenerator, abort: abort, confirm: Confirm, inform: Inform, modes: modes]; IF x # NIL THEN files _ LIST[x] ELSE files _ NIL; } ELSE files _ Spell.GetMatchingList[pattern: root, generator: fileGenerator, abort: abort, confirm: Confirm, inform: Inform, modes: modes]; }; IF files # NIL AND ext # NIL THEN FOR lst: LIST OF ROPE _ files, lst.rest UNTIL lst = NIL DO lst.first _ Rope.Cat[lst.first, ".", ext]; ENDLOOP; MessageWindow.Clear[]; IF unwaitCursor THEN WindowManager.UnWaitCursor[]; IF releaseGenerator THEN ReleaseGenerator[]; }; -- of GetMatchingFileList SetUpGenerator: PROC [ unknown: ROPE, defaultExt: ROPE, abort: AbortProc, confirm: ConfirmProc, inform: InformProc, modes: Modes ] RETURNS[root, ext, correct: ROPE] = { noExtension: BOOLEAN _ FALSE; i: INT _ Rope.Find[s1: unknown, s2: "."]; firstChar: CHAR; AcquireGenerator: ENTRY PROC = { IF fileGenerator = NIL THEN -- first time. TRUSTED { s: LONG STRING = UnsafeStorage.GetSystemUZone[].NEW[StringBody[128]]; pathName: ROPE; i, j: INT _ 0; [] _ Directory.GetNext[pathName: "WorkDir>", currentName: "", nextName: s]; pathName _ ConvertUnsafe.ToRope[s]; UNTIL (j _ Rope.Find[s1: pathName, s2: ">", pos1: i]) = -1 DO i _ j + 1; ENDLOOP; s.length _ i; fileGenerator _ GeneratorFromProcs[ generate: FileGenerate, clientData: fileGenState _ NEW[FileGenStateRecord _ [ pathName: s, scratchText: NEW[TEXT[128]], currentName: UnsafeStorage.GetSystemUZone[].NEW[StringBody[128]], nextName: UnsafeStorage.GetSystemUZone[].NEW[StringBody[128]] ]] ] } ELSE WHILE fileGenState.inUse DO WAIT fileGenState.nowFree ENDLOOP; fileGenState.inUse _ TRUE; IF fileCache = NIL THEN fileCache _ NEW[ARRAY CHARACTER['A..'Z] OF ROPE _ ALL[NIL]]; }; root _ unknown; ext _ defaultExt; IF i = -1 THEN { -- unknown does not have an extension IF defaultExt = NIL THEN { IF NOT IsAPattern[unknown] THEN noExtension _ TRUE} -- means only consider those without extension. The IsAPattern check is because Foo* means match everything. ELSE IF IsAPattern[defaultExt] THEN { -- e.g. "*press", meaning match .press and .mesa.press. only way to handle this is to treat the whole thing as a unit. root _ Rope.Cat[root, ".", defaultExt]; ext _ NIL; } } ELSE IF i = Rope.Length[unknown] - 1 -- i.e. of form foo. THEN {root _ Rope.Substr[base: unknown, len: i]; ext _ ""} ELSE IF IsAPattern[ext _ Rope.Substr[base: unknown, start: i + 1]] THEN -- treat whole name as unit {ext _ NIL; root _ unknown} ELSE { ext1: ROPE; root _ Rope.Substr[base: unknown, len: i]; ext1 _ GetFileExtension[unknown: ext, abort: abort, confirm: confirm, modes: modes]; IF ext1 = NIL THEN {root _ unknown; ext _ NIL} -- the extension is not recognized, so treat whole name as a unit. ELSE IF ext1 # ext THEN { -- was corrected r: ROPE = Rope.Cat[root, ".", ext1]; IF CheckForFile[r] THEN { -- extension was the only thing misspelled. return now correct name. IF inform # NIL THEN inform[Rope.Cat[unknown, " -> ", r]]; correct _ r; RETURN; } ELSE ext _ ext1;-- ext is known. spelling correct on root only. }; }; AcquireGenerator[]; fileGenState.ext _ RopeInline.InlineFlatten[ext]; fileGenState.noExtension _ noExtension; fileGenState.stopAfter _ LAST[CHARACTER]; TRUSTED {fileGenState.nextName.length _ 0; fileGenState.currentName.length _ 0}; fileGenState.scratchText.length _ 0; IF (firstChar _ Rope.Upper[Rope.Fetch[unknown, 0]]) IN ['A..'Z] THEN { fileGenState.stopAfter _ firstChar; FOR c: CHAR DECREASING IN ['A..firstChar] DO IF useFileCache AND NOT Rope.IsEmpty[fileCache[c]] AND CheckForFile[fileCache[c]] THEN TRUSTED { ConvertUnsafe.AppendRope[to: fileGenState.nextName, from: fileCache[c]]; EXIT; }; ENDLOOP; }; }; -- of SetUpGenerator ReleaseGenerator: ENTRY PROC = {fileGenState.inUse _ FALSE; NOTIFY fileGenState.nowFree}; FileGenState: TYPE = REF FileGenStateRecord _ NIL; FileGenStateRecord: TYPE = RECORD[ pathName, currentName, nextName: LONG STRING, scratchText: REF TEXT, ext: Rope.Text, noExtension: BOOLEAN _ FALSE, inUse: BOOL _ FALSE, stopAfter: CHARACTER _ IO.NUL, nowFree: CONDITION ]; fileGenerator: Spell.SpellingGenerator _ NIL; fileGenState: FileGenState _ NIL; fileCache: REF ARRAY CHARACTER['A..'Z] OF ROPE _ NIL; -- contains the last file just previous (in alphabetic order) to the indicated character. e.g. for B might be AtomsPrivate.BCD useFileCache: BOOL _ TRUE; buildFileCache: BOOL _ TRUE; FileGenerate: PROC [self: SpellingGenerator] RETURNS [REF TEXT] = TRUSTED { state: FileGenState _ NARROW[self.clientData]; ext: Rope.Text = state.ext; noExtension: BOOL = state.noExtension; pathLen: NAT = state.pathName.length; currentLen: NAT; len: NAT; s: LONG STRING; DO extDidntMatch: BOOL _ FALSE; s _ state.currentName; state.currentName _ state.nextName; state.nextName _ s; [] _ Directory.GetNext[pathName: state.pathName, currentName: state.currentName, nextName: state.nextName]; IF state.nextName.length = 0 THEN RETURN[NIL]; IF state.currentName.length = 0 OR Rope.Upper[state.nextName[pathLen]] > Rope.Upper[state.currentName[pathLen]] THEN -- new letter (assumes enumeration in alphabetic order) { cache: ROPE; len: INT; currentChar: CHAR = Rope.Upper[state.nextName[pathLen]]; IF currentChar > fileGenState.stopAfter THEN RETURN[NIL]; -- says that if you are doing a pattern match and first character is not a *, ok to stop as soon as you have passed that character. IF currentChar IN ['A..'Z] THEN { cache _ fileCache[currentChar]; len _ Rope.Length[cache]; FOR i: INT IN [0..state.currentName.length) DO IF i >= len OR state.currentName[i] # Rope.Fetch[cache, i] THEN { IF buildFileCache THEN fileCache[currentChar] _ ConvertUnsafe.ToRope[state.currentName]; -- insert in cache. EXIT; }; ENDLOOP; } }; currentLen _ state.nextName.length; len _ currentLen - pathLen; FOR i: NAT IN [pathLen..currentLen) DO char: CHARACTER _ state.nextName[i]; IF char # '. THEN NULL ELSE IF noExtension THEN GOTO Fail ELSE IF ext # NIL THEN -- NIL means look at all extensions, i.e. treat the entire name as a unit. { -- compare extension with ext. If not equal, do not consider this candidate. i1: NAT _ i + 1; FOR j: NAT IN [0..ext.length) DO IF i1 = currentLen THEN GOTO Fail; -- unaccounted for characters in target extension IF Rope.Lower[ext[j]] # Rope.Lower[state.nextName[i1]] THEN GOTO MaybeFail; -- target = .press and ext (at this point) is mesa.press i1 _ i1 + 1; REPEAT FINISHED => IF i1 < currentLen THEN GOTO MaybeFail; -- unaccounted for character in unknown extension, e.g. .press.press where target is .press ENDLOOP; len _ i - pathLen; extDidntMatch _ FALSE; EXIT; EXITS MaybeFail => extDidntMatch _ TRUE; }; state.scratchText[i - pathLen] _ char; REPEAT Fail => LOOP; FINISHED => IF (ext # NIL AND NOT noExtension) OR extDidntMatch THEN LOOP; -- to get here, either this file doesnt have an extension, or else ext = NIL, i.e. consider all files. This test says that if an extension is required, then it must be the case that this file does not have one, so reject it. ENDLOOP; state.scratchText.length _ len; RETURN[state.scratchText]; ENDLOOP; }; LoadCache: PROC = { [] _ SetUpGenerator[unknown: "zzz", defaultExt: NIL, abort: NIL, confirm: NIL, inform: NIL, modes: NIL]; DO IF FileGenerate[fileGenerator] = NIL THEN EXIT; ENDLOOP; ReleaseGenerator[] }; CheckForFile: PROC [file: ROPE] RETURNS [found: BOOLEAN] = TRUSTED { fName: LONG STRING; fName _ LOOPHOLE[Rope.ToRefText[file]]; found _ TRUE; IF Rope.Length[file] = 0 THEN RETURN[FALSE]; [] _ Directory.Lookup[fileName: fName, permissions: Directory.ignore ! Directory.Error => {found _ FALSE; CONTINUE} ]; }; TRUSTED {Process.Detach[FORK LoadCache[]]}; UserProfile.CallWhenProfileChanges[SetUp]; END. June 21, 1982 1:09 pm made patternMatch one of enumerated type of spelling classes. * This program implements the file spelling corrector. Last Edited by: Teitelman, March 12, 1983 1:28 pm file correction main body in the case that the extension is a known extension, the file generator is set up to only generate files of that extension, and the root is used as the unknown in the correction. main body file generator declaration see if this entry is already in the cache (avoid allocations). copy characters from long string into Rope.Text, skipping over pathName characters cloned from userexec to make spell totally independent. Κ– "Cedar" style˜J™JšΟc4™4J™1šΟk ˜ Jšœžœ˜)Jšœ žœ"˜1Jšžœžœ'žœ1žœ˜kJšœžœ˜$Jšœžœ ˜JšœžœDžœ"˜tJšœ žœ˜!Jšœžœ‡žœ#˜ΉJšœ žœ˜Jšœžœ˜%Jšœ žœ7˜HJšœžœ˜.J˜J˜—J˜JšΠbl œžœžœ˜J˜Jšžœžœ^˜‚J˜Jšžœ˜J˜šžœ ˜-J˜—J˜Jšž œ˜Iheadšœ™JšΟn œžœžœ€˜―J˜š   œžœžœžœžœ˜/Jšžœžœžœ˜šžœ/žœžœž˜BJšžœžœžœžœ˜-Jšž˜Jšžœžœ˜5Jšžœ˜—J˜—J˜Jš œžœ˜š œ%˜*JšœNžœ˜UJ˜—J˜š  œžœž˜Jšœ žœ˜Jšœ žœžœ˜Jšœžœ˜Jšœžœ˜Jšœžœ˜Jšœž˜Jšœ˜Jšžœ žœ˜Jšžœžœžœ˜Jšœžœ˜Jšœ žœ˜(Jšžœžœžœžœ˜*Jšžœ žœžœ˜/J˜ŽJš žœ žœžœžœ ˜[Jšœžœ;˜UJšœ>˜>J˜Jšžœžœžœ˜0Jšœ/žœ˜5Jšœžœ˜šœ˜šžœžœž˜J˜Jšžœžœ˜2Jšžœžœ˜,J˜—š œœ˜.šžœžœ)žœ ˜jJšžœ žœžœžœ˜—Jš žœžœžœ'žœ h˜ΓJ˜ J˜—š œKœ˜kJšœžœ˜ Jšžœžœ)žœά˜¦Jšžœžœžœ˜Jš žœ/žœžœžœ?˜…Jš žœ žœžœžœžœ˜$Jšœžœ˜J˜Jš žœžœžœ'žœ h˜ΓJšžœ(˜.Jšœ˜J˜—Jš ™ šžœžœ˜Jšœžœžœžœ˜Jšžœžœžœ žœžœžœ žœ˜FJ˜—šž˜JšœTžœ žœžœžœžœžœ žœžœžœžœ˜Έšžœ žœžœžœž œžœžœžœžœ˜ωšž ˜ Jšœžœž œ˜)J˜!J˜$JšœTžœ žœžœžœžœžœ žœžœžœžœ˜ΈJ˜——J˜—JšœE˜I—J˜š žœ žœžœžœž˜#J˜&—J˜Jšžœžœ!˜5Jšžœžœ˜,J˜—J˜š œž œ˜Jšœžœ˜Jšœ7˜7Jšœžœ˜Jšœžœ˜Jšœžœ˜Jšœžœ˜šœžœžœ˜"Jšœ žœ˜Jšœžœ˜ J˜"šžœ žœžœ$ž˜™>šžœ žœ ž˜!J˜J˜šžœžœžœž˜.šžœ žœ-ž˜AJšžœžœD˜mJšžœ˜J˜—Jšžœ˜—Jšœ˜—J˜——J˜#J˜JšR™Ršžœžœžœž˜&Jšœž œ˜$Jšžœ žœž˜Jšžœžœ žœžœ˜"š žœžœžœžœK˜bšœL˜NJšœžœ ˜šžœžœžœž˜ Jšžœžœžœ2˜UJšžœ5žœžœ 8˜…J˜ Jšž˜Jš žœžœžœžœ [œ˜‘Jšžœ˜—J˜Jšœžœ˜Jšžœ˜šž˜Jšœžœ˜"Jšœ˜———J˜'šž˜Jšœžœ˜ Jšžœžœžœžœžœžœžœžœΰ˜«—Jšžœ˜—J˜Jšžœ˜Jšžœ˜—Jšžœ˜—J˜š  œžœ˜Jš œ0žœ žœ žœ žœ žœ˜hšž˜Jšžœžœžœžœ˜/Jšžœ˜—J˜J˜—J˜J™7š  œžœžœžœ žœžœžœ˜EJšœžœžœ˜Jšœžœ˜'Jšœžœ˜ Jšžœžœžœžœ˜,˜D˜Jšœ žœžœ˜—Jšœ˜—šžœ˜J˜——Jšžœžœ˜+J˜*J˜Jšžœ˜J˜J˜SJ˜—…—@ΞUw