DIRECTORY Commander USING [CommandProc, CommandProcHandle, CommandProcObject, Enumerate, Handle, Lookup], CommandTool, Convert USING [Error, RopeFromLiteral], FileNames USING [ConvertToSlashFormat, CurrentWorkingDirectory, Directory, FileWithSearchRules, ResolveRelativePath], FS USING [Error, Open, OpenFile, nullOpenFile, StreamFromOpenFile], IO USING [Close, CreateStream, CreateStreamProcs, GetInfo, PutChar, PutRope, RopeFromROS, ROS, STREAM], IOUtils USING [closedStreamProcs], List USING [AList, Append, Assoc, CopyTopList, DotCons, Memb, PutAssoc, Remove], ProcessExtras USING [CheckForAbort], ProcessProps USING [AddPropList], Rope USING [Cat, Concat, Fetch, Find, IsEmpty, Length, Match, Replace, ROPE, Substr], RopeFrom USING [Stream]; CommandToolUtilitiesImpl: CEDAR PROGRAM IMPORTS Commander, CommandTool, Convert, FileNames, FS, IO, IOUtils, List, ProcessExtras, ProcessProps, Rope, RopeFrom EXPORTS CommandTool SHARES IO = BEGIN commandFileProcData: Commander.CommandProcHandle _ NEW[Commander.CommandProcObject _ [CommandTool.CommandFile]]; CopyAList: PUBLIC PROC [old: List.AList] RETURNS [new: List.AList] = { tail: List.AList _ NIL; new _ NIL; UNTIL old = NIL DO newItem: List.AList _ LIST[List.DotCons[key: old.first.key, val: old.first.val]]; IF tail = NIL THEN new _ newItem ELSE tail.rest _ newItem; old _ old.rest; tail _ newItem; ENDLOOP; }; PutLocalProperty: PUBLIC PROC [key, val: REF ANY, aList: List.AList, origList: List.AList _ NIL] RETURNS [List.AList] = { newList: List.AList; FOR l: List.AList _ aList, l.rest WHILE l # NIL AND l # origList DO IF l.first.key = key THEN { l.first.val _ val; RETURN[aList]; }; ENDLOOP; newList _ LIST[List.DotCons[key: key, val: val]]; newList.rest _ aList; RETURN[newList]; }; Insulate: PUBLIC PROC [stream: IO.STREAM] RETURNS [safeStream: IO.STREAM] = { safeStream _ IO.CreateStream[streamProcs: IO.CreateStreamProcs[variety: stream.GetInfo[].variety, class: stream.GetInfo[].class, close: IOUtils.closedStreamProcs.close], streamData: NIL, backingStream: stream]; }; CallList: PUBLIC PROC [property: REF ANY, cmd: Commander.Handle, proc: PROC [result: REF, msg: Rope.ROPE] RETURNS [stop: BOOL]] = { result: REF; msg: Rope.ROPE; WITH List.Assoc[key: property, aList: cmd.propertyList] SELECT FROM list: LIST OF REF ANY => FOR l: LIST OF REF ANY _ list, l.rest WHILE l # NIL DO WITH l.first SELECT FROM cpc: Commander.CommandProcHandle => { [result: result, msg: msg] _ cpc.proc[cmd]; cmd.propertyList _ List.PutAssoc[key: $Result, val: result, aList: cmd.propertyList]; IF proc # NIL AND proc[result: result, msg: msg] THEN EXIT; }; ENDCASE; ENDLOOP; ENDCASE; }; AddProcToList: PUBLIC PROC [aList: List.AList, listKey: REF ANY, proc: Commander.CommandProcHandle, append: BOOL _ TRUE] RETURNS [List.AList] = { maybeList: REF ANY _ List.Assoc[key: listKey, aList: aList]; list: LIST OF REF ANY; alreadyThere: BOOL; IF maybeList # NIL AND ISTYPE[maybeList, LIST OF REF ANY] THEN list _ NARROW[maybeList]; alreadyThere _ list # NIL AND List.Memb[ref: proc, list: list]; IF NOT alreadyThere THEN { IF append THEN { ra: REF ANY _ proc; list _ List.Append[list, LIST[ra]]; } ELSE list _ CONS[proc, list]; aList _ List.PutAssoc[key: listKey, val: list, aList: aList]; }; RETURN[aList]; }; RemoveProcFromList: PUBLIC PROC [aList: List.AList, listKey: REF ANY, proc: Commander.CommandProcHandle] RETURNS [List.AList] = { maybeList: REF ANY _ List.Assoc[key: listKey, aList: aList]; list: LIST OF REF ANY; alreadyThere: BOOL; IF maybeList # NIL AND ISTYPE[maybeList, LIST OF REF ANY] THEN list _ NARROW[maybeList]; alreadyThere _ list # NIL AND List.Memb[ref: proc, list: list]; IF alreadyThere THEN aList _ List.PutAssoc[key: listKey, val: List.Remove[ref: proc, list: list], aList: aList]; RETURN[aList]; }; FileWithSearchRules: PUBLIC PROC [root: Rope.ROPE, defaultExtension: Rope.ROPE, cmd: Commander.Handle] RETURNS [fullPath: Rope.ROPE _ NIL] = { RETURN[FileNames.FileWithSearchRules[root: root, defaultExtension: defaultExtension, searchRules: List.Assoc[key: $SearchRules, aList: cmd.propertyList]].fullPath]; }; ResolveRelativePath: PUBLIC PROC [path: Rope.ROPE] RETURNS [Rope.ROPE] = { RETURN[FileNames.ResolveRelativePath[path]]; }; ConvertToSlashFormat: PUBLIC PROC [path: Rope.ROPE] RETURNS [Rope.ROPE] = { RETURN[FileNames.ConvertToSlashFormat[path]]; }; CopyListOfRefAny: PUBLIC PROC [key: REF ANY, aList: List.AList] RETURNS [List.AList] = { ref: REF ANY _ List.Assoc[key: key, aList: aList]; IF ref = NIL THEN RETURN[aList]; WITH ref SELECT FROM lra: LIST OF REF ANY => RETURN[List.PutAssoc[key: key, val: List.CopyTopList[lra], aList: aList]]; ENDCASE; RETURN[aList]; }; AddSearchRule: PUBLIC PROC [cmd: Commander.Handle, dir: Rope.ROPE, append: BOOL _ TRUE] = { rules: LIST OF REF ANY; length: INT; first, last: CHAR; rules _ NARROW[List.Assoc[key: $SearchRules, aList: cmd.propertyList]]; IF dir = NIL THEN rules _ NIL ELSE { dir _ FileNames.ResolveRelativePath[dir]; length _ dir.Length[]; IF length < 3 THEN RETURN; first _ dir.Fetch[0]; last _ dir.Fetch[length - 1]; IF first # '[ AND first # '/ THEN RETURN; IF first = '[ AND last # '> THEN dir _ Rope.Concat[dir, ">"]; IF first = '/ AND last # '/ THEN dir _ Rope.Concat[dir, "/"]; IF append THEN { ra: REF ANY _ dir; rules _ List.Append[rules, LIST[ra]]; } ELSE rules _ CONS[dir, rules]; }; [] _ List.PutAssoc[key: $SearchRules, val: rules, aList: cmd.propertyList]; }; CurrentWorkingDirectory: PUBLIC PROC RETURNS [Rope.ROPE] = { RETURN[FileNames.CurrentWorkingDirectory[]]; }; LookupWithSearchRules: PUBLIC Commander.CommandProc = { root: Rope.ROPE _ cmd.command; wDir: Rope.ROPE _ NIL; wDirRoot: Rope.ROPE _ NIL; temp: Rope.ROPE _ NIL; ambiguous: BOOL _ List.Assoc[key: $Result, aList: cmd.propertyList] = $Ambiguous; rules: LIST OF REF ANY; fullPath: BOOL; Try: PROC [name: Rope.ROPE] RETURNS [foundSomething: BOOL] = { cmd.procData _ Commander.Lookup[name]; IF cmd.procData # NIL AND cmd.procData.proc # NIL THEN { cmd.command _ name; RETURN[TRUE]; }; cmd.procData _ NIL; RETURN[FALSE]; }; UniqueMatch: PROC [name: Rope.ROPE] RETURNS [foundSomething: BOOL _ FALSE] = { lst: LIST OF Rope.ROPE _ NIL; p: PROC [key: Rope.ROPE, procData: Commander.CommandProcHandle] RETURNS [stop: BOOL _ FALSE] = { IF procData.proc # NIL AND Rope.Match[pattern: name, object: key, case: FALSE] THEN lst _ CONS[key, lst]; }; name _ Rope.Concat[name, "*"]; [] _ Commander.Enumerate[p]; IF lst = NIL THEN RETURN[FALSE]; IF lst.rest # NIL THEN { -- ambiguous prefix ros: IO.STREAM _ IO.ROS[]; ambiguous _ TRUE; result _ $Ambiguous; ros.PutRope[" . . . command ambiguous ( "]; WHILE lst # NIL DO ros.PutRope[lst.first]; ros.PutChar[' ]; lst _ lst.rest; ENDLOOP; ros.PutRope[")\n"]; msg _ IO.RopeFromROS[ros]; RETURN[TRUE]; }; cmd.command _ lst.first; cmd.procData _ Commander.Lookup[cmd.command]; RETURN[TRUE]; }; result _ NIL; IF root.Length[] = 0 THEN RETURN[$Failure, NIL]; root _ FileNames.ResolveRelativePath[root]; root _ FileNames.ConvertToSlashFormat[root]; IF Try[root] THEN RETURN; fullPath _ root.Fetch[0] = '/; IF NOT fullPath THEN { wDir _ FileNames.CurrentWorkingDirectory[]; wDirRoot _ Rope.Concat[wDir, root]; IF Try[wDirRoot] THEN RETURN; rules _ NARROW[List.Assoc[key: $SearchRules, aList: cmd.propertyList]]; FOR list: LIST OF REF ANY _ rules, list.rest WHILE list # NIL DO IF Try[Rope.Concat[NARROW[list.first], root]] THEN RETURN; ENDLOOP; }; IF ambiguous OR UniqueMatch[root] THEN RETURN; IF NOT fullPath THEN { IF ambiguous OR UniqueMatch[wDirRoot] THEN RETURN; FOR list: LIST OF REF ANY _ rules, list.rest WHILE list # NIL DO IF ambiguous OR UniqueMatch[Rope.Concat[NARROW[list.first], root]] THEN RETURN; ENDLOOP; }; }; CommandFileWithSearchRules: PUBLIC Commander.CommandProc = { commandFileName: Rope.ROPE; ambiguous: BOOL _ List.Assoc[key: $Result, aList: cmd.propertyList] = $Ambiguous; [commandFileName, ambiguous] _ FileNames.FileWithSearchRules[root: cmd.command, defaultExtension: ".cm", requireExtension: FALSE, requireExact: ambiguous, searchRules: List.Assoc[key: $SearchRules, aList: cmd.propertyList]]; result _ NIL; IF ambiguous THEN result _ $Ambiguous; IF commandFileName.IsEmpty[] THEN RETURN; cmd.commandLine _ Rope.Concat[commandFileName, " "]; cmd.command _ "Commander"; cmd.procData _ commandFileProcData; }; LoadAndRunWithSearchRules: PUBLIC Commander.CommandProc = { commandFileName: Rope.ROPE; ambiguous: BOOL; sc: SavedCommand _ NIL; IF cmd.command.Find["."] # -1 THEN RETURN; ambiguous _ List.Assoc[key: $Result, aList: cmd.propertyList] = $Ambiguous; [commandFileName, ambiguous] _ FileNames.FileWithSearchRules[root: cmd.command, defaultExtension: ".load", requireExtension: TRUE, requireExact: ambiguous, searchRules: List.Assoc[key: $SearchRules, aList: cmd.propertyList]]; result _ NIL; IF ambiguous THEN result _ $Ambiguous; IF commandFileName.IsEmpty[] THEN RETURN; sc _ NEW[SavedCommandObject _ [command: cmd.command, commandFileName: commandFileName]]; cmd.procData _ NEW[Commander.CommandProcObject _ [ReallyLoadAndRun, "Load a .load file and retry", sc]]; cmd.command _ "ReallyLoadAndRun"; }; SavedCommand: TYPE = REF SavedCommandObject; SavedCommandObject: TYPE = RECORD [ command: Rope.ROPE _ NIL, commandFileName: Rope.ROPE _ NIL ]; ReallyLookupHandle: Commander.CommandProcHandle _ NEW[Commander.CommandProcObject _ [ReallyLoadAndRun, "ReallyLoadAndRun", "execute .load file and run command"]]; ReallyLoadAndRun: Commander.CommandProc = { sc: SavedCommand _ NARROW[cmd.procData.clientData]; commandFileName: Rope.ROPE _ sc.commandFileName; originalCommandLine: Rope.ROPE _ cmd.commandLine; oldOut: IO.STREAM; loadFileDirectory: Rope.ROPE; inner: PROC = { [result, msg] _ CommandTool.CommandFile[cmd]; }; commandFileName _ FileNames.ConvertToSlashFormat[commandFileName]; loadFileDirectory _ FileNames.Directory[path: commandFileName]; cmd.command _ "CommandTool"; cmd.commandLine _ commandFileName; cmd.procData _ commandFileProcData; oldOut _ cmd.out; cmd.out _ Insulate[cmd.err]; ProcessProps.AddPropList[List.PutAssoc[key: $WorkingDirectory, val: loadFileDirectory, aList: NIL], inner]; cmd.out _ oldOut; IF result = $Failure THEN RETURN[result, msg]; cmd.command _ sc.command; cmd.commandLine _ originalCommandLine; [result, msg] _ LookupWithSearchRules[cmd]; IF cmd.procData # NIL AND cmd.procData.proc # NIL THEN CommandTool.ExecuteCommand[cmd, FALSE] ELSE RETURN[$Failure, ".load file failed to register command"]; }; AtSignLimit: NAT _ 20; AtSignFile: PROC [name: Rope.ROPE] RETURNS [contents: Rope.ROPE] = { file: FS.OpenFile _ FS.nullOpenFile; fileStream: IO.STREAM; { ENABLE UNWIND => { IF fileStream # NIL THEN fileStream.Close[]; }; name _ FileNames.ResolveRelativePath[name]; file _ FS.Open[name: name ! FS.Error => IF error.group = user THEN CONTINUE]; IF file = FS.nullOpenFile THEN file _ FS.Open[name: Rope.Concat[name, ".cm"] ! FS.Error => IF error.group = user THEN CONTINUE]; IF file = FS.nullOpenFile THEN ERROR CommandTool.Failed[Rope.Concat["Can't find file ", name]]; fileStream _ FS.StreamFromOpenFile[openFile: file]; contents _ RopeFrom.Stream[stream: fileStream]; fileStream.Close[]; }; }; Pass1: PUBLIC PROC [initial: Rope.ROPE, nameOnly: BOOL] RETURNS [first: Rope.ROPE _ NIL, rest: Rope.ROPE _ NIL, terminator: CHAR _ '\n, someExpansion: BOOL _ FALSE] = { { state: {outside, insideQuotes, insideAtName} _ outside; c: CHAR; i: INT _ 0; atPosition: INT; atSignDepth: NAT _ 0; startPosition: INT _ 0; pastLeadingWhiteSpace: BOOL _ FALSE; IF NOT nameOnly THEN pastLeadingWhiteSpace _ TRUE; WHILE i < initial.Length[] DO ProcessExtras.CheckForAbort[]; { c _ initial.Fetch[i]; IF c = '\\ THEN { slashPosition: INT _ i; slashSequenceLength: INT _ 2; escapeRope: Rope.ROPE; i _ i + 1; -- pointing at char after the slash IF i >= initial.Length[] THEN ERROR CommandTool.Failed["Backslash at end of command"]; c _ initial.Fetch[i]; -- get the char after the slash IF c IN ['0..'9] THEN { IF (i + 2) >= initial.Length[] THEN ERROR CommandTool.Failed["Not enough characters for backslash convention"]; slashSequenceLength _ 4; }; escapeRope _ Convert.RopeFromLiteral[Rope.Cat["""", initial.Substr[start: slashPosition, len: slashSequenceLength], """"] ! Convert.Error => { ERROR CommandTool.Failed["Backslash sequence error"]}]; initial _ Rope.Replace[base: initial, start: slashPosition, len: slashSequenceLength, with: escapeRope]; i _ slashPosition; c _ initial.Fetch[i]; }; SELECT state FROM outside => { SELECT c FROM '" => { state _ insideQuotes; pastLeadingWhiteSpace _ TRUE; }; ';, '\n, '| => { terminator _ c; GOTO FoundTerminator; }; '@ => { state _ insideAtName; atPosition _ i; }; ' , ' => { IF nameOnly THEN { IF pastLeadingWhiteSpace THEN { terminator _ ' ; GOTO FoundTerminator; } ELSE startPosition _ startPosition + 1; }; }; '& => { IF nameOnly THEN { -- end of name terminator _ c; GOTO FoundTerminator; }; IF initial.Length[] > (i + 1) THEN { SELECT initial.Fetch[i+1] FROM ' , ' , '\n, '; => { -- followed by whitespace terminator _ c; GOTO FoundTerminator; }; ENDCASE => NULL; } ELSE { -- end of line terminator _ c; GOTO FoundTerminator; } }; ENDCASE => pastLeadingWhiteSpace _ TRUE; }; insideQuotes => { IF c = '" THEN { IF initial.Length[] > (i + 1) AND initial.Fetch[i+1] = '" THEN i _ i + 1 ELSE state _ outside; }; }; insideAtName => { IF c = '@ OR c = ' OR c = '\n THEN { nameLength: INT _ i - atPosition - 1; atSignFileName: Rope.ROPE _ initial.Substr[start: atPosition + 1, len: nameLength]; initial _ Rope.Replace[base: initial, start: atPosition, len: IF c = '@ THEN nameLength + 2 ELSE nameLength + 1, with: AtSignFile[name: atSignFileName]]; i _ atPosition - 1; state _ outside; atSignDepth _ atSignDepth + 1; IF atSignDepth > AtSignLimit THEN ERROR CommandTool.Failed["Exceeded limit on expansion of @ command files"]; someExpansion _ TRUE; }; }; ENDCASE => ERROR; i _ i + 1; }; REPEAT FoundTerminator => { rest _ Rope.Substr[base: initial, start: i + 1]; initial _ Rope.Substr[base: initial, start: startPosition, len: i - startPosition]; IF NOT nameOnly THEN initial _ Rope.Concat[initial, "\n"]; }; ENDLOOP; IF state = insideQuotes THEN ERROR CommandTool.Failed["Mismatched quotes"]; IF state = insideAtName THEN ERROR CommandTool.Failed["Improper @-file specification"]; }; first _ initial; }; END. October 7, 1983 9:38 am, Stewart, Created December 13, 1983 4:14 pm, Stewart, fixed bug in CallList CommandToolUtilitiesImpl.mesa Larry Stewart, December 13, 1983 6:28 pm CopyAList copies the CONS cells of the list itself and also copies the DotCons cells which are the elements of the list. Because the DotCons cells are copied, one can change the key-value mappings in the new list without affecting the mappings in the old list. Because the CONS cells are copied, one can alter the list without affecting the old list. PutLocalProperty is used to set a "local" property on a list. OrigList is intended to point into the middle of aList. If key is found on the part of aList in front of origList, then the old binding is changed and aList is returned. If key is not found on aList before origList is encountered, then a new property is added to the head of aList and the new list is returned. Insulate creates streams for which close appears to work, but which do not close the backing stream. It also attaches the in stream to the commander stop button. Note: looks like I have to do it by hand. Several functions in the command tool use properties which are bound to lists of command procs. The following functions help to manage this arrangement. CallList uses the given property name (typically an ATOM) to search cmd.propertyList. The value of the property should be LIST OF REF ANY. Each of the elements of the list should resolve to a Commander.CommandProcHandle. Each of the command procs found will be called with cmd as its argument. If proc is not NIL, it is called with the return values from each of the CommandProcs called. proc can stop CallList from proceeding by returning stop = TRUE; AddProcToList is used to construct or alter a LIST OF REF ANY whose elements are actually Commander.CommandProcHandles. RemoveProcToList is used to remove a particular Commander.CommandProcHandle from a LIST OF REF ANY whose elements are actually Commander.CommandProcHandles. FileWithSearchRules uses the following rules: If path starts with ./ or ../, ResolveRelativePath converts it into the equivalent full path name using the $WorkingDirectory property on the process properties list. If path is exactly . or .., ResolveRelativePath converts it to the current or parent directory. CopyListOfRefAny searches for the binding of the given key. If it is a LIST OF REF ANY, then it is List.CopyTopList-ed and put back. If rule = NIL THEN deletes all search rules. If append = FALSE, then dir becomes the first rule LookupWithSearchRules: get previous result via $Result property try root If root is not full path name try Concat[$WorkingDirectory, root] For each search rule try Concat[search rule, root] try to find a unique match for Concat[root, "*"]; If root is not full path name try to find a unique match for Cat[$WorkingDirectory, root, "*"]; For each search rule try to find a unique match for Cat[search rule, root, "*"]; Return result = $Ambiguous if either more than one possibility was found or if a unique result was found as a result of a pattern match. Exactly one match try root try Concat[$WorkingDirectory, root] For each search rule try Concat[search rule, root] try Concat[search rule, root] try to find a unique match for Concat[root, "*"]; try to find a unique match for Cat[$WorkingDirectory, root, "*"]; For each search rule try to find a unique match for Cat[search rule, root, "*"]; CommandFileWithSearchRules: look in the FS directory for a command file If $Result is $Ambiguous on entry, then only try exact matches Return $Ambiguous if FileWithSearchRules does LoadAndRunWithSearchRules: look in the FS directory for a file with extension ".load" If found, run it then attempt a normal commander lookup (using LookupWithSearchRules). If $Result is $Ambiguous on entry, then only try exact matches Return $Ambiguous if FileWithSearchRules does We have located a command file, although perhaps ambiguous. If it is, we don't want to run it just yet. Set up the cmd so that if run, it will continue with ReallyLoadAndRun below These are used when LoadAndRun has to save the original command information procData.clientData is a SavedCommand, load the .load file whose name is in commandLine and then Lookup and execute the saved command. smash the working directory for this call I have used RopeFrom.Stream[FS.StreamFromOpenFile[]] rather than RopeFrom.File[FS.Open[]] because I believe that the stream will correctly handle Tioga source but that RopeFrom.File will not. Pass1 handles the initial rope passed in from ReadEvalPrint. It searches for the first ';, if there is one, and restricts its attention to the part of the rope before it. Êà˜šœ™Jšœ(™(—J˜šÏk ˜ Jšœ œP˜_Jšœ ˜ Jšœœ˜'Jšœ œf˜uJšœœ;˜CJšœœRœœ˜gJšœœ˜"JšœœF˜PJšœœ˜$Jšœ œ˜!Jšœœ=œ ˜UJšœ œ ˜—J˜šœœ˜'Jšœ-œœ<˜vJšœ ˜Jšœ˜ Jšœ˜—™Jšœ3œ:˜pJ˜—Jšœß™ßšÏn œœœœ˜FJšœœ˜Jšœœ˜ šœœ˜Jšœœ7˜QJšœœœœ˜:Jšœ˜Jšœ˜Jšœ˜—J˜—J˜Jšœ÷™÷šžœœœ œœ,œœ˜yJšœ˜š œœœœ˜Cšœœ˜Jšœ˜Jšœ˜J˜—Jšœ˜—Jšœ œ#˜1J˜Jšœ ˜J˜—J˜J™¢šžœœœ œœœœœ˜MJ™)Jšœ œœŠœ˜ÒJ˜—J˜J™™J™JšœÉ™Éšžœœœ œœœ œ œœœ˜ƒJšœœ˜ Jšœ œ˜šœ4œ˜Cš œœœœœ˜šœœœœœœœ˜6šœ œ˜šœ%˜%Jšœ+˜+JšœU˜UJš œœœ œœ˜;J˜—Jšœ˜—Jšœ˜——Jšœ˜—J˜—J˜Jšœw™wšž œœœœœ-œœœ˜‘Jšœ œœ*˜Jšœ&˜&š œœœœœ˜8Jšœ˜Jšœœ˜ J˜—Jšœœ˜Jšœœ˜J˜J˜—š ž œœ œœœœ˜NJš œœœœœ˜š œœ œ)œœœ˜`Jš œœœ.œœœ ˜iJšœ˜—J˜Jšœ˜Jšœ˜Jš œœœœœ˜ šœ œœÏc˜-Jš œœœœœ˜Jšœ œ˜Jšœ˜Jšœ+˜+šœœ˜Jšœ˜Jšœ˜J˜Jšœ˜—Jšœ˜Jšœœ˜Jšœœ˜ J˜—J™Jšœ˜Jšœ-˜-Jšœœ˜ J˜—J™Jšœ œ˜ Jšœœœ œ˜0Jšœ+˜+Jšœ,˜,J˜J™Jšœ œœ˜J™Jšœ˜J˜šœœ œ˜J™#J˜+Jšœ#˜#Jšœœœ˜J™Jšœœ9˜G™J™—šœœœœœœœ˜@J™Jšœœœœ˜:Jšœ˜—J˜—J™J™1Jšœ œœœ˜.J˜Jšœœ œ˜™J™AJšœ œœœ˜2J™™J™;—šœœœœœœœ˜@Jš œ œœœœ˜OJšœ˜—J˜—J˜J˜—J˜J˜JšœG™GJ™>Jšœ-™-šœœ˜Jšœ-™-J™šœœ˜;Jšœœ˜Jšœ œ˜Jšœœ˜Jšœœœ˜*JšœK˜KJšœ}œ`˜áJšœ œ˜ Jšœ œ˜&Jšœœœ˜)J™hJšœK™KJšœœP˜XJšœœV˜hJšœ!˜!J˜J˜—J™KJšœœœ˜,J˜šœœœ˜#Jšœœœ˜Jšœœ˜ J˜—J˜Jšœ2œm˜¢J˜Jšœ†™†šœ+˜+Jšœœ˜3Jšœœ˜0Jšœœ˜1Jšœœœ˜Jšœœ˜Jšœœ5˜@JšœB˜BJšœ?˜?Jšœ˜Jšœ"˜"J˜#J˜Jšœ˜J™)Jšœ^œ ˜kJšœ˜Jšœœœ˜.Jšœ˜Jšœ&˜&Jšœ+˜+Jš œœœœœ!œ˜]Jšœœ4˜?J˜—J˜Jšœ œ˜š ž œœ œœœ˜DJšœœ œ˜$Jšœ œœ˜Jš œœœœœœ˜DJšœ+˜+Jš œœœ œœœ˜MJšœœœœ'œ œœœ˜€Jšœœœœ;˜_Jšœœ¡™¿Jšœ œ$˜3Jšœ/˜/Jšœ˜J˜J˜—J™J™«šžœœœœ œœœœ œœœœœ˜¨J˜J˜7Jšœœ˜Jšœœ˜ Jšœ œ˜Jšœ œ˜Jšœœ˜Jšœœœ˜$Jšœœ œœ˜2šœ˜J˜J˜Jšœ˜šœ œ˜Jšœœ˜Jšœœ˜Jšœœ˜Jšœ Ÿ#˜/Jšœœœ3˜VJšœŸ˜6šœœ œ˜JšœœœF˜oJ˜J˜—šœŽ˜ŽJšœ2˜7—Jšœh˜hJ˜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šœœœ ˜HJšœ˜J˜—J˜—šœ˜šœœœ œ˜$Jšœ œ˜%Jšœœ:˜SJšœ>œœœ9˜™J˜Jšœ˜J˜JšœœœF˜mJšœœ˜J˜—J˜—Jšœœ˜—J˜ J˜Jš˜˜Jšœ0˜0JšœS˜SJšœœ œ'˜;J˜—Jšœ˜—Jšœœœ)˜KJšœœœ5˜WJ˜J˜J˜—J˜Jšœ˜J˜J˜)J˜9—…—8(]