DIRECTORY BasicTime USING [GMT, nullGMT], DFUtilities USING [Date, DateToStream, DifferenceOfUsingLists, DirectoryItem, FileItem, Filter, ImportsItem, IncludeItem, ParseFromStream, ProcessItemProc, RemoveVersionNumber, SortUsingList, SyntaxError, UsingForm, UsingList], FS USING [Error, FileInfo, GetInfo, GetName, Open, OpenFile, StreamFromOpenFile], GenerateDFClosure USING [ActionKind, ActionProc, ClosureInfo, Options], IO USING [Close, PutRope, STREAM], MessagesOut USING [PutMsg, PutRopes], Process USING [CheckForAbort, Detach], Rope USING [Concat, Equal, Fetch, Length, ROPE], SafeStorage USING [ReclaimCollectibleObjects], SymTab USING [Create, EachPairAction, Fetch, Pairs, Ref, Store]; GenerateDFClosureImpl: CEDAR MONITOR LOCKS state USING state: State IMPORTS DFUtilities, FS, IO, MessagesOut, Process, Rope, SafeStorage, SymTab EXPORTS GenerateDFClosure = BEGIN ActionKind: TYPE = GenerateDFClosure.ActionKind; ActionProc: TYPE = GenerateDFClosure.ActionProc; ClosureInfo: TYPE = GenerateDFClosure.ClosureInfo; Date: TYPE = DFUtilities.Date; DirectoryItem: TYPE = DFUtilities.DirectoryItem; FileItem: TYPE = DFUtilities.FileItem; FileItemList: TYPE = LIST OF FileItem; Filter: TYPE = DFUtilities.Filter; AllFilter: Filter = [FALSE, all, all, all, NIL]; FutureImportsItem: TYPE = RECORD [imp: REF ImportsItem, from: REF]; ImportsItem: TYPE = DFUtilities.ImportsItem; IncludeItem: TYPE = DFUtilities.IncludeItem; LORA: TYPE = LIST OF REF ANY; Options: TYPE = GenerateDFClosure.Options; ROPE: TYPE = Rope.ROPE; RopeList: TYPE = LIST OF ROPE; STREAM: TYPE = IO.STREAM; UsingForm: TYPE = DFUtilities.UsingForm; UsingList: TYPE = DFUtilities.UsingList; ImportsPair: TYPE = RECORD [ from: ROPE, -- the original source of this import needed: REF ImportsItem, -- the imports not yet done for this DF file doing: REF ImportsItem -- the imports in progress (or done) for this DF file ]; InclDefList: TYPE = LIST OF InclDefItem; InclDefItem: TYPE = RECORD[incl: REF, from: REF, filter: Filter]; ImpDefList: TYPE = LIST OF ImpDefItem; ImpDefItem: TYPE = RECORD[imp: REF ImportsItem, from: REF, filter: Filter]; State: TYPE = REF StateRep; StateRep: TYPE = MONITORED RECORD [ head,tail: LORA _ NIL, -- head and tail of file list inclHead,inclTail: InclDefList _ NIL, -- head&tail for deferred include processing impHead,impTail: ImpDefList _ NIL, -- head&tail for deferred imports processing errs: STREAM _ NIL, -- error reporting stream (can be NIL) action: ActionProc _ NIL, -- the client's callback proc actionData: REF _ NIL, -- the data for the client's callback proc tab: SymTab.Ref _ NIL, -- table of DF files (ImportsPair & IncludeItem objects) forked,started,finished: INT _ 0, -- counts associated with forked processes files: INT _ 0, -- files in the closure (including DF files) dfFiles: INT _ 0, -- DF files examined notFound: INT _ 0, -- # of DF files not found maxProcesses: NAT _ 16, -- # of forked processes besides main process serverRetries: NAT _ 10, -- # of times to retry when server is unavailable abortRequested: BOOL _ FALSE, -- TRUE if an abort was requested followImports: BOOL _ FALSE, -- TRUE if imports are followed messages: BOOL _ FALSE, -- TRUE if debug info goes in message window ProcessStarted: CONDITION, -- BROADCAST for every forked process start ProcessFinished: CONDITION -- BROADCAST for every forked process finish ]; GenerateClosureToProc: PUBLIC PROC [dfName: ROPE, errs: STREAM, action: ActionProc, data: REF, options: Options _ []] RETURNS [info: ClosureInfo _ [0, 0, 0]] = { state: State _ NEW[StateRep _ [ action: action, actionData: data, errs: errs, messages: options.messages, followImports: options.followImports, maxProcesses: options.toFork, serverRetries: options.serverRetries]]; { ENABLE UNWIND => NoteAborting[state]; GenerateClosure[state, dfName]; info _ [state.files, state.dfFiles, state.notFound]; }; SmashTheState[state]; PutMsg[state, "Done."]; }; GenerateClosureToStream: PUBLIC PROC [dfName: ROPE, errs: STREAM, out: STREAM, options: Options _ [], verbose: BOOL _ FALSE] RETURNS [info: ClosureInfo _ [0, 0, 0]] = { state: State _ NEW[StateRep _ [ errs: errs, messages: options.messages, maxProcesses: options.toFork, serverRetries: options.serverRetries]]; IF out # NIL THEN { ENABLE UNWIND => NoteAborting[state]; GenerateClosure[state, dfName]; info _ [state.files, state.dfFiles, state.notFound]; DumpFiles[state, state.head, out, verbose]; }; SmashTheState[state]; PutMsg[state, "Done."]; }; GenerateClosure: PROC [state: State, dfName: ROPE] = { lag: LORA _ NIL; IF state.tab = NIL THEN state.tab _ SymTab.Create[361, FALSE]; DoProcessInclude[ state: state, incl: NEW[IncludeItem _ [dfName, [omitted], NIL, FALSE]], from: NIL, filter: AllFilter]; WaitForIncludesDone[state]; DO processPair: SymTab.EachPairAction = { quit _ FALSE; WITH val SELECT FROM impP: REF ImportsPair => { needed: REF ImportsItem _ impP.needed; copy: REF ImportsItem _ NIL; nList: REF UsingList _ needed.list; nForm: UsingForm = needed.form; IF nForm = list AND (nList = NIL OR nList.nEntries = 0) THEN RETURN; IF (copy _ NoteDoing[state, impP]) # NIL THEN { anyChanged _ TRUE; SELECT nForm FROM all => { ProcessInclude[state, needed, impP.from, [filterB: public]]; RETURN; }; exports => ProcessInclude[state, needed, impP.from, [filterB: public]]; ENDCASE; IF nList # NIL AND nList.nEntries # 0 THEN ProcessInclude[state, copy, impP.from, [list: nList]]; }; }; ENDCASE; }; anyChanged: BOOL _ FALSE; oldTail: LORA _ state.tail; IF lag # oldTail THEN { PutMsg[state, "Finding possible imports."]; IF lag = NIL THEN lag _ state.head ELSE lag _ lag.rest; FOR each: LORA _ lag, each.rest WHILE each # NIL DO WITH each.first SELECT FROM impF: REF FutureImportsItem => { ProcessImport[state, impF.imp, impF.from, AllFilter]; }; ENDCASE; lag _ each; ENDLOOP; WaitForIncludesDone[state]; anyChanged _ oldTail # state.tail; }; PutMsg[state, "Fetching imports."]; [] _ SymTab.Pairs[state.tab, processPair]; WaitForImportsDone[state]; IF NOT anyChanged THEN EXIT; ENDLOOP; }; DumpFiles: PROC [state: State, list: LORA, out: STREAM, verbose: BOOL _ FALSE] = { putFile: PROC [name: ROPE, date: Date, plus: BOOL _ FALSE] = { IF plus THEN IO.PutRope[out, "+"]; IO.PutRope[out, name]; IO.PutRope[out, "\t\t"]; DFUtilities.DateToStream[out, date]; }; putImp: PROC [imp: REF ImportsItem, future: BOOL _ FALSE] = { usingRope: ROPE _ "\n USING ["; list: REF UsingList _ imp.list; IF future THEN {IO.PutRope[out, "\n -- FUTURE IMPORTS "]; usingRope _ "\n -- USING ["} ELSE IO.PutRope[out, "\n\nIMPORTS "]; putFile[imp.path1, imp.date]; SELECT imp.form FROM exports => {IO.PutRope[out, usingRope]; IO.PutRope[out, "PUBLIC]"]}; all => {IO.PutRope[out, usingRope]; IO.PutRope[out, "ALL]"]}; ENDCASE; IO.PutRope[out, usingRope]; IF list # NIL THEN FOR i: NAT IN [0..list.nEntries) DO IF i > 0 THEN IO.PutRope[out, ", "]; IF list[i].verifyRoot THEN IO.PutRope[out, "+"]; IO.PutRope[out, list[i].name]; ENDLOOP; IO.PutRope[out, "]"]; }; PutMsg[state, "Dumping list of files."]; WHILE list # NIL DO ENABLE UNWIND => IO.Close[out]; CheckAbort[state]; WITH list.first SELECT FROM file: REF FileItem => { IO.PutRope[out, "\n "]; putFile[file.name, file.date, file.verifyRoot]; }; ENDCASE; IF verbose THEN WITH list.first SELECT FROM file: REF FileItem => {}; imp: REF ImportsItem => { putImp[imp]; }; impF: REF FutureImportsItem => { putImp[impF.imp, TRUE]; }; incl: REF IncludeItem => { IO.PutRope[ out, "\n\nINCLUDES "]; putFile[incl.path1, incl.date]; }; ENDCASE => IO.PutRope[out, "\n ????"]; list _ list.rest; ENDLOOP; IO.PutRope[out, "\n\n"]; }; ProcessInclude: ENTRY PROC [state: State, incl: REF, from: REF, filter: Filter] = TRUSTED { ENABLE UNWIND => state.abortRequested _ TRUE; new: InclDefList = LIST[[incl, from, filter]]; CheckAbortInternal[state]; IF state.inclHead = NIL THEN state.inclHead _ new ELSE state.inclTail.rest _ new; state.inclTail _ new; IF state.forked-state.finished < state.maxProcesses THEN { state.forked _ state.forked + 1; Process.Detach[FORK ProcessIncludeBase[state]]; WHILE state.forked > state.started DO CheckAbortInternal[state]; WAIT state.ProcessStarted; ENDLOOP; }; }; ProcessIncludeBase: PROC [state: State] = { ENABLE { UNWIND => Unwind[state]; ABORTED => {Unwind[state]; GO TO abort}; }; BumpStarted[state]; DO list: InclDefList _ RemInclude[state, TRUE]; IF list = NIL THEN RETURN; DoProcessInclude[state, list.first.incl, list.first.from, list.first.filter]; ENDLOOP; EXITS abort => {}; }; WaitForIncludesDone: PROC [state: State] = { DO list: InclDefList = RemInclude[state]; IF list = NIL THEN EXIT; DoProcessInclude[state, list.first.incl, list.first.from, list.first.filter]; ENDLOOP; WaitForForkedDoneOrAbort[state]; }; RemInclude: ENTRY PROC [state: State, finish: BOOL _ FALSE] RETURNS [list: InclDefList _ NIL] = { ENABLE UNWIND => state.abortRequested _ TRUE; CheckAbortInternal[state]; list _ state.inclHead; IF list # NIL THEN { IF (state.inclHead _ list.rest) = NIL THEN state.inclTail _ NIL; list.rest _ NIL; RETURN; }; IF finish THEN BumpFinishedInternal[state]; }; DoProcessInclude: PROC [state: State, incl: REF, from: REF, filter: Filter] = { subHead: LORA _ LIST[incl]; subTail: LORA _ subHead; currentDirectory: REF DirectoryItem _ NIL; inStream: STREAM _ NIL; nFiles: INT _ 0; selfSeen: BOOL _ FALSE; anySeen: BOOL _ FALSE; processItem: DFUtilities.ProcessItemProc = { CheckAbort[state]; WITH item SELECT FROM dir: REF DirectoryItem => { currentDirectory _ dir; }; file: REF FileItem => { IF currentDirectory # NIL THEN file.name _ Rope.Concat[currentDirectory.path1, file.name]; IF state.action = NIL THEN { subTail.rest _ LIST[file]; subTail _ subTail.rest; } ELSE { state.action[state.actionData, file, file.name, file.date, fullName]; }; nFiles _ nFiles + 1; anySeen _ TRUE; IF Rope.Equal[file.name, fullName, FALSE] OR Rope.Equal[file.name, initName, FALSE] THEN selfSeen _ TRUE; }; imp: REF ImportsItem => { SELECT TRUE FROM NOT state.followImports => RETURN; filter.filterB = public AND NOT imp.exported => RETURN; filter.list # NIL => {imp.form _ list; imp.list _ filter.list}; ENDCASE; subTail.rest _ LIST[NEW[FutureImportsItem _ [imp: imp, from: incl]]]; subTail _ subTail.rest; }; inclP: REF IncludeItem => { ProcessInclude[state, inclP, incl, filter]; }; ENDCASE; }; initName, fullName: ROPE _ NIL; kind: ROPE; tab: SymTab.Ref _ state.tab; date: Date; isIncluded: BOOL _ FALSE; checkRemote: BOOL _ FALSE; WITH incl SELECT FROM incl1: REF IncludeItem => {date _ incl1.date; initName _ incl1.path1; isIncluded _ TRUE}; imp1: REF ImportsItem => {date _ imp1.date; initName _ imp1.path1}; ENDCASE => RETURN; IF date.format # explicit THEN { checkRemote _ TRUE; initName _ DFUtilities.RemoveVersionNumber[initName]; IF CheckPreviousInclusion[state, initName] THEN RETURN; }; [inStream, fullName, date] _ OpenRead[state, initName, date]; IF inStream = NIL THEN { BumpNotFound[state]; PutMsg[state, initName, " (not found)"]; IF state.action # NIL THEN state.action[state.actionData, notFound, initName, date, ParentName[from]]; RETURN; }; IF CheckPreviousInclusion[state, fullName] THEN RETURN; WITH incl SELECT FROM incl1: REF IncludeItem => {incl1.path1 _ fullName}; imp1: REF ImportsItem => {imp1.path1 _ fullName}; ENDCASE => RETURN; SELECT TRUE FROM isIncluded => { kind _ " (all)"; [] _ SymTab.Store[tab, fullName, incl]; IF checkRemote THEN [] _ SymTab.Store[tab, initName, incl]; }; filter.filterB = public => kind _ " (public)"; filter.list # NIL => kind _ " (list)"; ENDCASE => kind _ " ??"; PutMsg[state, fullName, kind]; initName _ DFUtilities.RemoveVersionNumber[fullName]; DFUtilities.ParseFromStream[inStream, processItem, filter ! DFUtilities.SyntaxError => { PutRopes[state, "Syntax error in ", fullName, "\n ", reason]; IF state.action # NIL THEN state.action[state.actionData, syntaxError, fullName, date, ParentName[from]]; CONTINUE; }]; IO.Close[inStream]; SpliceFileList[state, subHead, subTail, nFiles, 1]; }; ProcessImport: ENTRY PROC [state: State, imp: REF ImportsItem, from: REF, filter: Filter] = TRUSTED { ENABLE UNWIND => state.abortRequested _ TRUE; new: ImpDefList = LIST[[imp, from, filter]]; CheckAbortInternal[state]; IF state.impHead = NIL THEN state.impHead _ new ELSE state.impTail.rest _ new; state.impTail _ new; IF state.forked-state.finished < state.maxProcesses THEN { state.forked _ state.forked + 1; Process.Detach[FORK ProcessImportBase[state]]; WHILE state.forked > state.started DO CheckAbortInternal[state]; WAIT state.ProcessStarted; ENDLOOP; }; }; ProcessImportBase: PROC [state: State] = { ENABLE { UNWIND => Unwind[state]; ABORTED => {Unwind[state]; GO TO abort}; }; BumpStarted[state]; DO list: ImpDefList _ RemImport[state, TRUE]; IF list = NIL THEN RETURN; DoProcessImport[state, list.first.imp, list.first.from, list.first.filter]; ENDLOOP; EXITS abort => {}; }; WaitForImportsDone: PROC [state: State] = { DO list: ImpDefList = RemImport[state]; IF list = NIL THEN EXIT; DoProcessImport[state, list.first.imp, list.first.from, list.first.filter]; ENDLOOP; WaitForIncludesDone[state]; }; RemImport: ENTRY PROC [state: State, finish: BOOL _ FALSE] RETURNS [list: ImpDefList _ NIL] = { ENABLE UNWIND => state.abortRequested _ TRUE; list _ state.impHead; CheckAbortInternal[state]; IF list # NIL THEN { IF (state.impHead _ list.rest) = NIL THEN state.impTail _ NIL; list.rest _ NIL; RETURN; }; IF finish THEN BumpFinishedInternal[state]; }; DoProcessImport: PROC [state: State, imp: REF ImportsItem, from: REF, filter: Filter] = { initName: ROPE _ imp.path1; fullName: ROPE _ initName; attached: ROPE; created: BasicTime.GMT _ imp.date.gmt; ok: BOOL _ TRUE; checkRemote: BOOL _ imp.date.format # explicit; tab: SymTab.Ref = state.tab; ExcludeFilterAndMerge: PROC [impP: REF ImportsPair] = { SELECT TRUE FROM imp.form = list AND (imp.list = NIL OR imp.list.nEntries = 0) => RETURN; filter.list # NIL => { IF imp.form = list THEN { common: REF UsingList _ filter.list; diff: REF UsingList _ DFUtilities.DifferenceOfUsingLists[common, imp.list]; IF diff # NIL THEN common _ DFUtilities.DifferenceOfUsingLists[filter.list, diff]; IF common = NIL OR common.nEntries = 0 THEN RETURN; imp _ NEW[ImportsItem _ imp^]; imp.list _ common; }; }; filter.filterB = public AND NOT imp.exported => RETURN; ENDCASE; MergeUsingLists[state, impP, imp]; }; IF imp.list # NIL THEN DFUtilities.SortUsingList[imp.list, TRUE]; IF checkRemote THEN { imp.path1 _ initName _ fullName _ DFUtilities.RemoveVersionNumber[initName]; created _ BasicTime.nullGMT; WITH SymTab.Fetch[tab, initName].val SELECT FROM incl: REF IncludeItem => { RETURN; }; impP: REF ImportsPair => { ExcludeFilterAndMerge[impP]; RETURN; }; ENDCASE; }; {ENABLE FS.Error => IF error.group # bug THEN GO TO noGot; PutMsg[state, "Checking ", fullName]; [fullFName: fullName, attachedTo: attached, created: created] _ FS.FileInfo[fullName, created, checkRemote]; IF attached # NIL THEN fullName _ attached; imp.path1 _ fullName; imp.date _ [explicit, created]; WITH SymTab.Fetch[tab, fullName].val SELECT FROM incl: REF IncludeItem => { RETURN; }; impP: REF ImportsPair => { ExcludeFilterAndMerge[impP]; RETURN; }; ENDCASE; { needed: REF ImportsItem _ NEW[ImportsItem _ imp^]; doing: REF ImportsItem _ NEW[ImportsItem _ imp^]; fromName: ROPE _ ParentName[from]; impPair: REF ImportsPair _ NEW[ImportsPair _ [from: fromName, needed: needed, doing: doing]]; doing.list _ NIL; doing.form _ list; [] _ SymTab.Store[tab, fullName, impPair]; IF state.action = NIL THEN { file: REF FileItem _ NEW[FileItem _ [name: fullName, date: imp.date, verifyRoot: FALSE]]; list: LORA _ LIST[file]; SpliceFileList[state, list, list, 1, 0]; } ELSE { state.action[state.actionData, file, fullName, imp.date, fullName]; state.files _ state.files + 1; }; IF checkRemote THEN [] _ SymTab.Store[tab, initName, impPair]; }; EXITS noGot => { PutRopes[state, "Import not found: ", fullName, "\n from: ", ParentName[from]]; }; }; }; SpliceFileList: ENTRY PROC [state: State, subHead,subTail: LORA, nFiles,ndfFiles: INT _ 0] = { ENABLE UNWIND => state.abortRequested _ TRUE; IF state.head = NIL THEN state.head _ subHead ELSE state.tail.rest _ subHead; state.tail _ subTail; state.files _ state.files + nFiles; state.dfFiles _ state.dfFiles + ndfFiles; }; BumpStarted: ENTRY PROC [state: State] = { ENABLE UNWIND => state.abortRequested _ TRUE; state.started _ state.started + 1; BROADCAST state.ProcessStarted; CheckAbortInternal[state]; }; BumpFinished: ENTRY PROC [state: State] = { ENABLE UNWIND => state.abortRequested _ TRUE; BumpFinishedInternal[state]; }; BumpFinishedInternal: INTERNAL PROC [state: State] = { state.finished _ state.finished + 1; BROADCAST state.ProcessFinished; CheckAbortInternal[state] }; BumpNotFound: ENTRY PROC [state: State] = { state.notFound _ state.notFound + 1; }; BumpFiles: ENTRY PROC [state: State] = { state.files _ state.files + 1; }; WaitForForkedDone: ENTRY PROC [state: State] = { WHILE state.forked > state.finished DO WAIT state.ProcessFinished; ENDLOOP; }; WaitForForkedDoneOrAbort: ENTRY PROC [state: State] = { ENABLE UNWIND => state.abortRequested _ TRUE; WHILE state.forked > state.finished DO CheckAbortInternal[state]; WAIT state.ProcessFinished; ENDLOOP; }; Unwind: ENTRY PROC [state: State] = { state.abortRequested _ TRUE; state.finished _ state.finished + 1; BROADCAST state.ProcessFinished; WHILE state.forked > state.finished DO WAIT state.ProcessFinished; ENDLOOP; }; OpenRead: PROC [state: State, name: ROPE, date: Date] RETURNS [in: STREAM _ NIL, fullName: ROPE _ NIL, realDate: Date] = { attached: ROPE _ NIL; created: BasicTime.GMT _ BasicTime.nullGMT; checkRemote: BOOL _ date.format # explicit; why: ROPE _ NIL; retries: NAT _ 0; file: FS.OpenFile; realDate _ date; IF checkRemote THEN fullName _ DFUtilities.RemoveVersionNumber[name] ELSE {fullName _ name; created _ date.gmt}; DO file _ FS.Open[fullName, $read, created, checkRemote ! FS.Error => { SELECT error.code FROM $serverInaccessible => { PutRopes[state, "Server glitch: ", fullName]; retries _ retries + 1; IF retries <= state.serverRetries THEN LOOP; }; ENDCASE; PutRopes[state, "Can't open ", fullName, "\n ", error.explanation]; IF error.group # bug THEN GO TO noGot; }]; in _ FS.StreamFromOpenFile[file, $read]; EXIT; ENDLOOP; [fullFName: fullName, attachedTo: attached] _ FS.GetName[file]; created _ FS.GetInfo[file].created; IF attached # NIL THEN fullName _ attached; IF created # BasicTime.nullGMT THEN realDate _ [format: explicit, gmt: created]; EXITS noGot => {}; }; MergeUsingLists: ENTRY PROC [state: State, impP: REF ImportsPair, imp: REF ImportsItem] = { ENABLE UNWIND => state.abortRequested _ TRUE; needed: REF ImportsItem _ impP.needed; doing: REF ImportsItem _ impP.doing; impList: REF UsingList _ CopyUsingList[imp.list]; CheckAbortInternal[state]; IF impList # NIL THEN { IF needed.list # NIL THEN impList _ DFUtilities.DifferenceOfUsingLists[impList, needed.list]; IF doing.list # NIL AND impList # NIL THEN impList _ DFUtilities.DifferenceOfUsingLists[impList, doing.list]; IF impList = NIL OR impList.nEntries = 0 THEN RETURN; }; SELECT imp.form FROM all => {needed.form _ all; needed.list _ NIL}; exports => IF needed.form = list THEN needed.form _ exports; list => { nList: REF UsingList = impList; oList: REF UsingList = needed.list; SELECT TRUE FROM nList = NIL => {}; oList = NIL => needed.list _ nList; ENDCASE => { diff: REF UsingList = DFUtilities.DifferenceOfUsingLists[nList, oList]; IF diff # NIL THEN { dLen: NAT = diff.nEntries; oLen: NAT = oList.nEntries; SELECT TRUE FROM dLen = 0 => {}; oLen = 0 => needed.list _ diff; ENDCASE => { nnlist: REF UsingList _ needed.list _ NEW[UsingList[oLen+dLen]]; nnlist.nEntries _ oLen+dLen; FOR i: NAT IN [0..oLen) DO nnlist[i] _ oList[i]; ENDLOOP; FOR j: NAT IN [0..dLen) DO nnlist[j+oLen] _ diff[j]; ENDLOOP; DFUtilities.SortUsingList[nnlist, oLen > dLen]; }; }; }; }; ENDCASE => ERROR; }; NoteDoing: ENTRY PROC [state: State, impP: REF ImportsPair] RETURNS [REF ImportsItem _ NIL] = { ENABLE UNWIND => state.abortRequested _ TRUE; needed: REF ImportsItem _ impP.needed; nList: REF UsingList _ needed.list; nLen: NAT _ IF nList = NIL THEN 0 ELSE nList.nEntries; nForm: UsingForm _ needed.form; doing: REF ImportsItem _ impP.doing; dList: REF UsingList _ doing.list; dLen: NAT _ IF dList = NIL THEN 0 ELSE dList.nEntries; Copy: PROC RETURNS [toDo: REF ImportsItem _ NIL] = { toDo _ NEW[ImportsItem _ needed^]; toDo.list _ nList; toDo.form _ nForm; }; needed.list _ NIL; needed.form _ list; SELECT doing.form FROM all => RETURN; exports => { SELECT nForm FROM all => { doing.form _ all; doing.list _ NIL; RETURN [doing]; }; ENDCASE => { nForm _ list; IF nLen = 0 THEN RETURN; }; }; list => { SELECT nForm FROM all => { doing.form _ all; doing.list _ NIL; RETURN [doing]; }; exports => { doing.form _ exports; IF nLen = 0 THEN RETURN [Copy[]]; }; list => { IF nLen = 0 THEN RETURN; }; ENDCASE; }; ENDCASE; IF dLen = 0 THEN doing.list _ nList ELSE { diff: REF UsingList = DFUtilities.DifferenceOfUsingLists[nList, dList]; delta: NAT = IF diff = NIL THEN 0 ELSE diff.nEntries; IF delta = 0 THEN RETURN; nLen _ dLen+delta; nList _ doing.list _ NEW[UsingList[nLen]]; nList.nEntries _ nLen; FOR i: NAT IN [0..dLen) DO nList[i] _ dList[i]; ENDLOOP; FOR j: NAT IN [0..delta) DO nList[j+dLen] _ diff[j]; ENDLOOP; DFUtilities.SortUsingList[nList, delta <= dLen]; }; RETURN [Copy[]]; }; CopyUsingList: PROC [old: REF UsingList] RETURNS [new: REF UsingList _ NIL] = { IF old # NIL THEN { size: NAT = old.nEntries; new _ NEW[UsingList[size]]; new.nEntries _ size; FOR i: NAT IN [0..size) DO new[i] _ old[i]; ENDLOOP; }; }; ParentName: PROC [from: REF] RETURNS [parent: ROPE] = { parent _ "??"; WITH from SELECT FROM rope: ROPE => parent _ rope; imp: REF ImportsItem => parent _ imp.path1; inclP: REF IncludeItem => parent _ inclP.path1; ENDCASE; }; CheckPreviousInclusion: PROC [state: State, name: ROPE, msg: BOOL _ FALSE] RETURNS [BOOL] = { WITH SymTab.Fetch[state.tab, name].val SELECT FROM incl: REF IncludeItem => { IF msg THEN PutMsg[state, name, " (skipped)"]; RETURN [TRUE]; }; ENDCASE => RETURN [FALSE]; }; NoteAborting: PROC [state: State] = { state.abortRequested _ TRUE; PutRopes[state, "Aborting...\n\n"]; WaitForForkedDone[state]; SmashTheState[state]; }; SmashTheState: ENTRY PROC [state: State] = { ENABLE UNWIND => NULL; list: LORA _ state.head; WHILE list # NIL DO rest: LORA _ list.rest; list.rest _ NIL; list _ rest; ENDLOOP; state.head _ NIL; state.tail _ NIL; state.tab _ NIL; SafeStorage.ReclaimCollectibleObjects[]; }; PutRopes: PROC [state: State, r1,r2,r3,r4: ROPE _ NIL] = { ENABLE UNWIND => state.abortRequested _ TRUE; IF state.abortRequested THEN RETURN WITH ERROR ABORTED; MessagesOut.PutRopes[state.errs, r1,r2,r3,r4]; }; PutMsg: PROC [state: State, r1,r2,r3,r4: ROPE _ NIL] = { ENABLE UNWIND => state.abortRequested _ TRUE; IF state.abortRequested THEN RETURN WITH ERROR ABORTED; MessagesOut.PutMsg[r1,r2,r3,r4]; }; CheckAbort: ENTRY PROC [state: State] = { ENABLE UNWIND => NULL; CheckAbortInternal[state]; }; CheckAbortInternal: INTERNAL PROC [state: State] = { ENABLE UNWIND => { state.abortRequested _ TRUE; BROADCAST state.ProcessFinished; BROADCAST state.ProcessStarted; }; IF state.abortRequested THEN { BROADCAST state.ProcessFinished; BROADCAST state.ProcessStarted; RETURN WITH ERROR ABORTED; }; Process.CheckForAbort[]; }; ScanName: PROC [name: ROPE] RETURNS [pos,bang,dot: INT] = { len: INT = Rope.Length[name]; pos _ bang _ dot _ len; WHILE pos > 0 DO posM: INT = pos-1; SELECT Rope.Fetch[name, posM] FROM '! => bang _ dot _ posM; '. => dot _ posM; '>, '/, '] => RETURN; ENDCASE; pos _ posM; ENDLOOP; }; END. ¾GenerateDFClosureImpl.mesa Copyright c 1985, 1986 by Xerox Corporation. All rights reserved. Russ Atkinson (RRA) April 30, 1986 5:43:59 pm PDT T Y P E S PUBLIC routines ... generates the closure of files mentioned by the given DF file. Files may appear in the closure twice, although this is not frequent for well-structured DF files. For each file in the closure, the caller's action routine is invoked with the file name and other information (see above). The action routine may be invoked from as many as 1+options.toFork processes, so the client may need to be careful about concurrent actions. ... performs roughly the same actions as GenerateClosureToProc, except that the closure so generated is written to the given output stream (which is left open at the end). If verbose = TRUE, then information about IMPORTS and INCLUDES is also written to the file. Main routines Generates the closure of the given DF file under the given filter. The first thing to do is to expand the included files, so we see the maximum number of DF files in the first pass. This will cut off unnecessary processing of includes and imports in later stages. At this point we need to fill in the imports that are outstanding. This takes place in two repeated phases: the first one processes FutureImportsItem objects on the list into ImportsPair objects in the DF table. The second phase turns ImportsPair objects into FileItem objects on the list. We stop when there has been a pass without any changes. [key: Key, val: Val] RETURNS [quit: BOOL] There is an ImportsPair object to be processed. We first capture the needed using list. Then we move the needed using list to the doing using list to prevent excess processing. Finally, we use ProcessInclude to handle whatever items pass the filter. There are some new imports to be handled. There are FutureImportsItem objects on the list that we have not yet processed. Therefore, we scan the unprocessed section of the list for such items, and process them, which merges the various imports lists, and leaves ImportsPair items in the DF table for imported DF files that were NOT included in the first pass. Generate the ImportsPair items in the DF table, filling in the imports as necessary. ... dumps the list to the named file. If NOT verbose, then only file names will be dumped. Otherwise, all items on the list will be printed in some form. Include processing Base of forked process to process an included DF file Whether or not there are other processes to help us, the current process can help make progress towards removing some of the queued imports processing. ... removes an entry from the deferred includes list. NIL will be returned if there are no more entries. To avoid races between testing for no entries and insertion of new entries, if finish is TRUE, then the customary finish processing is performed when the returned value is NIL. ... processes an include or imports item under the given filter. For each item found in the given file under the filter, we take the following actions: DirectoryItem => set current directory (within this invocation of DoProcessInclude) FileItem => add file to the list ImportsItem => add the ImportsItem to the list (for more processing) IncludeItem => recursively process the inclusion [item: REF ANY] RETURNS [stop: BOOL _ FALSE] The version number is a hint, but we don't really want that hint stored. In this case we can check for a previous inclusion of the file. Try to open the file for reading. At the same time, we get the REAL full name for the file. Complain about the file not being there! Try for a previous inclusion of this file. For a full inclusion of this file, we enter the inclusion into the table to cut off further unnecessary inclusions and imports of this file. we will find this file under the initial name whenever we try for it Imports processing Base of forked process to process an imported DF file Whether or not there are other processes to help us, the current process can help make progress towards removing some of the queued imports processing. The imports processing may have added some new includes. So we wait for it to have subsided. ... removes an entry from the deferred includes list. NIL will be returned if there are no more entries. To avoid races between testing for no entries and insertion of new entries, if finish is TRUE, then the customary finish processing is performed when the returned value is NIL. Now we toss out whatever we can due to the filter given us. Now, merge the using lists. The version number is useless, so let's strip it off so we don't have further problems. In this case, the imported file has already been included. In this case, the imported file has been previously imported, so we just merge the using lists. The file must be checked for explicit version number. Therefore, we first get the real name from the file system. This includes following what the file is attached to. In this case, the imported file has already been included, so we do not need to further process it. However, we do note that it has been already processed. In this case, the imported file has been previously imported, so we just merge the using lists. create the imports pair for table insertion Every DF file imported should be in the enumeration Utilities ... waits for all forked processes to complete. Aborts are not recognized. ... waits for all forked processes to complete OR for an abort to be requested. ... opens the given name with the given date, and returns an open stream (or NIL if the file could not be opened) and the real full name of the file. This is an entry procedure to keep from blundering into multiple merges of using lists. The using list of impP.needed is modified to include the using list of imp (minus the current lists of impP.needed and impP.doing). We assume that imp.list # NIL <=> imp.form = list. Cast out the entries from the USING lists of both the known need and the known doing imports items. If we are left with no new items, then return. Merge the new and old lists. ... transfers the using list from impP.needed to impP.doing. We return a new ImportsItem if there is something to do. NoteDoing is an entry procedure to keep from blundering into multiple merges of using lists. Do various quick kill tests to determine if there is something new to do. Nothing was happening for this import, so just move the needed list. Sigh, there are items in the doing using list. We need to merge in the non-common items from the needed using list (provided that there are non-common items). Merge the new and old lists. Remember that we have to keep the new list sorted. ... checks the table for previous inclusion of the given name. If msg, then put out a message if this file was included. This procedure vigorously throws away the file list and the DF table. It helps in recovering storage, since it is likely that the conservative scan will hold onto some cell in the list, thereby causing numerous others to be held. .. puts out the given ropes to the command output stream. This is an entry procedure to keep from confusing the STREAM abstraction, which is not protected against concurrent access. ... puts out the given ropes to the MessageWindow. This is an entry procedure to keep the message in the MessageWindow consistent. Ê#N˜codešœ™Kšœ Ïmœ7™BK™1—˜šÏk ˜ Kšœ žœžœ ˜Kšœ žœÒ˜ãKšžœžœI˜QKšœžœ0˜GKšžœžœžœ˜"Kšœ žœ˜%Kšœžœ˜&Kšœžœ žœ˜0Kšœ žœ˜.Kšœžœ4˜@K˜——šÏnœžœž˜$Kšžœžœ ˜Kšžœžœžœ1˜LKšžœ˜Kšœž˜K˜—šœ ™ K˜Kšœ žœ ˜0Kšœ žœ ˜0Kšœ žœ!˜2Kšœžœ˜Kšœžœ˜0Kšœ žœ˜&Kšœžœžœžœ ˜&šœžœ˜"Kšœžœžœ˜0—Kš œžœžœžœžœ˜CKšœ žœ˜,Kšœ žœ˜,Kš žœžœžœžœžœžœ˜Kšœ žœ˜*Kšžœžœžœ˜Kš œ žœžœžœžœ˜Kšžœžœžœžœ˜Kšœ žœ˜(Kšœ žœ˜(K˜šœ žœžœ˜KšœžœÏc%˜2Kšœžœ ,˜EKšœžœ  5˜LK˜K˜—Kšœ žœžœžœ ˜(Kš œ žœžœžœžœ˜AK˜Kšœ žœžœžœ ˜&Kš œ žœžœžœžœ˜KK˜Kšœžœžœ ˜šœ žœž œžœ˜#Kšœ žœžœ ˜5Kšœ!žœ ,˜RKšœžœ ,˜OKšœžœžœ &˜;Kšœžœ ˜7Kšœ žœžœ *˜BKšœžœ 8˜PKšœžœ *˜LKšœžœ ,˜=Kšœ žœ ˜'Kšœ žœ ˜.Kšœžœ -˜FKšœžœ 1˜KKšœžœžœ !˜?Kšœžœžœ ˜K˜KšœÅ™Åšœ˜K˜ Kšœžœ#žœžœ˜9Kšœžœ˜ Kšœ˜—Kšœ˜K™KšœÝ™Ýšž˜šœ&˜&Kšœ)™)Kšœžœ˜ šžœžœž˜šœžœ˜Kšœü™üKšœžœ˜&Kšœžœžœ˜Kšœžœ˜#Kšœ˜Kš žœžœ žœžœžœžœ˜Dšžœ#žœžœ˜/Kšœ)™)Kšœ žœ˜šžœž˜šœ˜Kšœ<˜Kšžœžœžœ˜"Kšžœ˜Kšžœ˜Kšœ$˜$K˜—š œžœžœžœžœ˜=Kšœ žœ˜"Kšœžœ˜šžœ˜ KšžœžœH˜PKšžœžœ˜%—Kšœ˜šžœ ž˜Kšœ žœžœ˜DKšœžœžœ˜=Kšžœ˜Kšžœ˜—šžœžœž˜šžœžœžœž˜#Kšžœžœžœ˜$Kšžœžœžœ˜0Kšžœ˜Kšžœ˜——Kšžœ˜K˜—Kšœ(˜(šžœžœž˜Kšžœžœžœ ˜Kšœ˜šžœ žœž˜šœžœ˜Kšžœ˜Kšœ/˜/K˜—Kšžœ˜—šžœ ž˜šžœ žœž˜Kšœžœ˜šœžœ˜Kšœ ˜ Kšœ˜—šœžœ˜ Kšœžœ˜K˜—šœžœ˜Kšžœ ˜"Kšœ˜K˜—šžœ˜ Kšžœ˜———K˜Kšžœ˜—Kšžœ˜K˜—K˜—šœ™K™š Ÿœžœžœžœžœžœ˜[Kšžœžœžœ˜-Kšœžœ˜.Kšœ˜šžœž˜Kšžœ˜Kšžœ˜—Kšœ˜šžœ2žœ˜:Kšœ ˜ Kšœžœ˜/šžœž˜%Kšœ˜Kšžœ˜Kšžœ˜—K˜—K˜K˜—šŸœžœ˜+šžœ˜Kšžœ˜Kšžœžœžœ˜(K˜—Kšœ5™5Kšœ˜šž˜Kšœ&žœ˜,Kšžœžœžœžœ˜KšœM˜MKšžœ˜—Kšžœ ˜K˜K˜—šŸœžœ˜,šž˜Kšœ—™—Kšœ&˜&Kšžœžœžœžœ˜KšœM˜MKšžœ˜—Kšœ ˜ Kšœ˜K˜—šŸ œžœžœžœžœžœžœ˜aKšœ7žœŠžœOžœ™›Kšžœžœžœ˜-Kšœ˜Kšœ˜šžœžœžœ˜Kšžœ žœžœžœ˜@Kšœ žœ˜Kšžœ˜K˜—Kšžœžœ˜+Kšœ˜K˜—šŸœžœžœžœ˜Ošœ˜™˜KšœS™SKšœ ™ KšœD™DKšœ0™0—Kšœ žœžœ˜Kšœ žœ ˜Kšœžœžœ˜*Kšœ žœžœ˜Kšœžœ˜Kšœ žœžœ˜Kšœ žœžœ˜šœ,˜,Kš œžœžœžœžœžœ™,Kšœ˜šžœžœž˜šœžœ˜Kšœ˜K˜—šœžœ˜šžœžœž˜Kšœ;˜;—šžœž˜šžœ˜Kšœžœ˜Kšœ˜K˜—šžœ˜KšœE˜EK˜——Kšœ˜Kšœ žœ˜šžœ!žœžœ!žœ˜SKšžœ žœ˜—K˜—šœžœ˜šžœžœž˜Kšžœžœ˜"Kšœžœžœžœ˜7Kšœžœ.˜?Kšžœ˜—Kšœžœžœ.˜EKšœ˜K˜—šœžœ˜Kšœ+˜+K˜—Kšžœ˜—K˜—Kšœžœžœ˜Kšœžœ˜ K˜K˜ Kšœ žœžœ˜Kšœ žœžœ˜K˜šžœžœž˜KšœžœIžœ˜YKšœžœ:˜CKšžœžœ˜—šžœžœ˜ Kšœ‰™‰Kšœžœ˜Kšœ5˜5Kšžœ)žœžœ˜7K˜K˜—Kšœ\™\Kšœ=˜=K˜šžœ žœžœ˜Kšœ(™(K˜Kšœ(˜(šžœžœž˜KšœK˜K—Kšžœ˜K˜—K™Kšœ*™*Kšžœ)žœžœ˜7K˜šžœžœž˜Kšœžœ)˜3Kšœžœ(˜1Kšžœžœ˜K˜—šžœžœž˜šœ˜KšœŒ™ŒKšœ˜Kšœ'˜'šžœ ž˜KšœD™DKšœ'˜'—Kšœ˜—Kšœ.˜.Kšœžœ˜&Kšžœ˜—Kšœ˜K˜Kšœ5˜5šœ9˜9šœ˜Kšœ>˜>šžœžœž˜KšœN˜N—Kšžœ˜ Kšœ˜——Kšžœ˜Kšœ3˜3K˜K˜——K™šœ™K˜š Ÿ œžœžœžœžœžœ˜eKšžœžœžœ˜-Kšœžœ˜,Kšœ˜šžœž˜Kšžœ˜Kšžœ˜—Kšœ˜šžœ2žœ˜:Kšœ ˜ Kšœžœ˜.šžœž˜%Kšœ˜Kšžœ˜Kšžœ˜—K˜—K˜K˜—šŸœžœ˜*šžœ˜Kšžœ˜Kšžœžœžœ˜(K˜—Kšœ5™5Kšœ˜šž˜Kšœ$žœ˜*Kšžœžœžœžœ˜KšœK˜KKšžœ˜—Kšžœ ˜K˜K˜—šŸœžœ˜+šž˜Kšœ—™—Kšœ$˜$Kšžœžœžœžœ˜KšœK˜KKšžœ˜—Kšœ]™]Kšœ˜Kšœ˜K˜—šŸ œžœžœžœžœžœžœ˜_Kšœ7žœŠžœOžœ™›Kšžœžœžœ˜-Kšœ˜Kšœ˜šžœžœžœ˜Kšžœžœžœžœ˜>Kšœ žœ˜Kšžœ˜K˜—Kšžœžœ˜+Kšœ˜K˜—šŸœžœžœžœ˜YKšœ žœ ˜Kšœ žœ ˜Kšœ žœ˜Kšœžœ˜&Kšœžœžœ˜Kšœ žœ˜/Kšœ˜šŸœžœžœ˜7Kšœ;™;šžœžœž˜Kš œžœ žœžœžœ˜Hšœžœ˜šžœžœ˜Kšœžœ˜$KšœžœB˜KKšžœžœžœ@˜RKš žœ žœžœžœžœ˜3Kšœžœ˜Kšœ˜K˜—K˜—Kšœžœžœžœ˜7Kšžœ˜—Kšœ™Kšœ"˜"K˜—K˜Kšžœ žœžœ%žœ˜Ašžœ žœ˜KšœW™WKšœL˜LKšœ˜šžœ!žœž˜0šœžœ˜Kšœ:™:Kšžœ˜K˜—šœžœ˜Kšœ_™_Kšœ˜Kšžœ˜K˜—Kšžœ˜—K˜—Kšœ©™©š œžœžœ žœžœžœžœ˜:Kšœ%˜%šœ?˜?Kšžœ*˜,—Kšžœ žœžœ˜+Kšœ˜Kšœ˜šžœ!žœž˜0šœžœ˜Kšœœ™œKšžœ˜K˜—šœžœ˜Kšœ_™_Kšœ˜Kšžœ˜K˜—Kšžœ˜—šœ˜Kšœ+™+Kšœžœžœ˜2Kšœžœžœ˜1Kšœ žœ˜"šœ žœ˜Kšžœ?˜B—Kšœ žœ˜Kšœ˜Kšœ*˜*Kšœ3™3šžœž˜šžœ˜Kšœžœ žœ9žœ˜YKšœžœžœ˜Kšœ(˜(K˜—šžœ˜KšœC˜CK˜K˜——Kšžœ žœ+˜>K˜—šžœ ˜KšœP˜PKšœ˜—K˜—K˜K˜——K™šœ ™ K˜š Ÿœžœžœ!žœžœ ˜^Kšžœžœžœ˜-Kšžœžœžœžœ˜MKšœ˜Kšœ#˜#Kšœ)˜)K˜K˜—šŸ œžœžœ˜*Kšžœžœžœ˜-Kšœ"˜"Kšž œ˜Kšœ˜Kšœ˜K˜—šŸ œžœžœ˜+Kšžœžœžœ˜-Kšœ˜Kšœ˜K˜—šŸœžœžœ˜6Kšœ$˜$Kšž œ˜ Kšœ˜Kšœ˜K˜—šŸ œžœžœ˜+Kšœ$˜$Kšœ˜K˜—šŸ œžœžœ˜(Kšœ˜Kšœ˜K˜—šŸœžœžœ˜0KšœK™KKšžœžœžœžœ˜KKšœ˜K˜—šŸœžœžœ˜7Kšœ/žœ™OKšžœžœžœ˜-šžœž˜&Kšœ˜Kšžœ˜Kšžœ˜—Kšœ˜K˜—šŸœžœžœ˜%Kšœžœ˜Kšœ$˜$Kšž œ˜ Kšžœžœžœžœ˜KK˜K˜—šŸœžœžœ žœžœžœ žœžœ˜zKšœ•™•Kšœ žœžœ˜Kšœžœ˜+Kšœ žœ˜+Kšœžœžœ˜Kšœ žœ˜Kšœ˜Kšœ˜šžœ ˜Kšžœ1˜5Kšžœ'˜+—šž˜šœ4˜4šœžœ ˜šžœ ž˜šœ˜Kšœ-˜-K˜Kšžœ žœžœ˜,K˜—Kšžœ˜—KšœD˜DKšžœžœžœžœ˜&Kšœ˜——Kšœžœ!˜(Kšžœ˜Kšžœ˜—Kšœ.žœ˜?Kšœ žœ˜#Kšžœ žœžœ˜+Kšžœžœ-˜PKšžœ ˜K˜K˜—š Ÿœžœžœžœžœ˜[Kšœøžœ™Kšžœžœžœ˜-Kšœžœ˜&Kšœžœ˜$Kšœ žœ%˜1Kšœ˜šžœ žœžœ˜Kšœ“™“šžœžœžœ˜KšœC˜C—š žœžœžœ žœž˜*KšœB˜B—Kš žœ žœžœžœžœ˜5K˜—šžœ ž˜Kšœ)žœ˜.Kšœ žœžœ˜<˜ Kšœžœ˜Kšœžœ˜#šžœžœž˜Kšœžœ˜Kšœžœ˜#šžœ˜ Kšœžœ>˜Gšžœžœžœ˜Kšœžœ˜Kšœžœ˜šžœžœž˜Kšœ˜Kšœ˜šžœ˜ Kšœ™Kšœžœžœ˜@Kšœ˜Kš žœžœžœ žœžœ˜9Kš žœžœžœ žœžœ˜=Kšœ/˜/K˜——K˜—K˜——K˜—Kšžœžœ˜—K˜K˜—šŸ œžœžœžœžœžœžœ˜_KšœÔ™ÔKšžœžœžœ˜-Kšœžœ˜&Kšœžœ˜#Kš œžœžœ žœžœžœ˜6Kšœ˜Kšœžœ˜$Kšœžœ˜"Kš œžœžœ žœžœžœ˜6š Ÿœžœžœžœžœ˜4Kšœžœ˜"Kšœ˜Kšœ˜K˜—Kšœžœ˜K˜KšœI™Išžœ ž˜Kšœžœ˜šœ ˜ šžœž˜šœ˜Kšœžœ˜#Kšžœ ˜Kšœ˜—šžœ˜ K˜ Kšžœ žœžœ˜K˜——K˜—šœ ˜ šžœž˜šœ˜Kšœžœ˜#Kšžœ ˜Kšœ˜—šœ ˜ Kšœ˜Kšžœ žœžœ ˜!K˜—šœ ˜ Kšžœ žœžœ˜K˜—Kšžœ˜—K˜—Kšžœ˜—šžœ ˜ šž˜KšœD™DKšœ˜—šžœ˜KšœŸ™ŸKšœžœ>˜GKš œžœžœžœžœžœ˜5Kšžœ žœžœ˜KšœP™PKšœ˜Kšœžœ˜*Kšœ˜Kš žœžœžœ žœžœ˜8Kš žœžœžœ žœžœ˜=Kšœ0˜0K˜——Kšžœ ˜K˜K˜—š Ÿ œžœžœ žœžœ žœ˜Ošžœžœžœ˜Kšœžœ˜Kšœžœ˜K˜Kš žœžœžœ žœžœ˜4K˜—K˜K˜—š Ÿ œžœžœžœ žœ˜7Kšœ˜šžœžœž˜Kšœžœ˜Kšœžœ#˜+Kšœžœ%˜/Kšžœ˜—Kšœ˜K˜—šŸœžœžœžœžœžœžœ˜]Kšœy™yšžœ#žœž˜2šœžœ˜Kšžœžœ#˜.Kšžœžœ˜Kšœ˜—Kšžœžœžœ˜—K˜K˜—šŸ œžœ˜%Kšœžœ˜Kšœ#˜#Kšœ˜Kšœ˜K˜K˜—šŸ œžœžœ˜,Kšœæ™æKšžœžœžœ˜Kšœžœ˜šžœžœž˜Kšœžœ ˜Kšœ žœ˜K˜ Kšžœ˜—Kšœ žœ˜Kšœ žœ˜Kšœ žœ˜Kšœ(˜(K˜K˜—šŸœžœžœžœ˜:Kšœ¶™¶Kšžœžœžœ˜-Kš žœžœžœžœžœžœ˜7Kšœ.˜.K˜K˜—šŸœžœžœžœ˜8Kšœƒ™ƒKšžœžœžœ˜-Kš žœžœžœžœžœžœ˜7Kšœ ˜ K˜K˜—šŸ œžœžœ˜)Kšžœžœžœ˜Kšœ˜K˜K˜—šŸœžœžœ˜4šžœžœ˜Kšœžœ˜Kšž œ˜ Kšž œ˜K˜—šžœžœ˜Kšž œ˜ Kšž œ˜Kšžœžœžœžœ˜K˜—Kšœ˜K˜K˜—š Ÿœžœžœžœžœ˜;Kšœžœ˜Kšœ˜šžœ ž˜Kšœžœ ˜šžœž˜"Kšœ˜Kšœ˜Kšœžœ˜Kšžœ˜—K˜ Kšžœ˜—K˜K˜——Kšžœ˜K˜—…—\†œ’