DIRECTORY Atom USING [MakeAtom], Basics USING [], BasicTime USING [GMT, Now, nullGMT], Commander USING [CommandProc, Register], CommandTool USING [ArgumentVector, Failed, Parse], Convert USING [AppendInt], DFUtilities USING [DirectoryItem, FileItem, IncludeItem, ParseFromStream, ProcessItemProc, SyntaxError], FileNames USING [FileWithSearchRules, GetShortName], FS USING [EnumerateForNames, Error, GetName, NameProc, OpenFileFromStream, StreamOpen], IO USING [Close, EndOfStream, GetCedarToken, GetChar, GetIndex, PeekChar, PutChar, PutF1, PutFR, PutFR1, PutRope, SkipWhitespace, STREAM, TokenKind], List USING [Compare, CompareProc, PutAssoc, Sort], Process USING [CheckForAbort], RefText USING [InlineAppendChar, TrustTextAsRope], Rope USING [ActionType, Compare, Concat, Fetch, Flatten, FromRefText, Length, Map, Match, ROPE, SkipOver, SkipTo], SymTab USING [Create, FetchText, Ref, Store], TiogaAccess USING [Create, Put, TiogaChar, WriteFile, Writer]; XRef: CEDAR PROGRAM IMPORTS Atom, BasicTime, Commander, CommandTool, Convert, DFUtilities, FileNames, FS, IO, List, Process, RefText, Rope, SymTab, TiogaAccess = BEGIN ROPE: TYPE = Rope.ROPE; STREAM: TYPE = IO.STREAM; IdEntry: TYPE = RECORD [ name: ROPE, refs: LIST OF IdRef _ NIL, reserved: ATOM _ NIL]; IdRef: TYPE = RECORD [ fileName: ROPE, -- file name where reference occurs index: INT, -- index where reference occurs defn: BOOL]; -- indicates a defining reference DoCommandProc: Commander.CommandProc = { out: STREAM = cmd.out; switches: PACKED ARRAY CHAR['a..'z] OF BOOL _ ALL[FALSE]; ProcessSwitches: PROC [arg: ROPE] = { sense: BOOL _ TRUE; FOR index: INT IN [0..Rope.Length[arg]) DO char: CHAR _ Rope.Fetch[arg, index]; SELECT char FROM '- => LOOP; '~ => {sense _ NOT sense; LOOP}; 'x, 'X => IF filesRead # 0 THEN DumpXref[xrefName]; IN ['a..'z] => switches[char] _ sense; IN ['A..'Z] => switches[char + ('a-'A)] _ sense; ENDCASE; sense _ TRUE; ENDLOOP; }; EachStream: PROC [stream: STREAM] = { blockLevel: INTEGER _ 0; parenLevel: INTEGER _ 0; lastId: REF IdEntry _ NIL; lastPos: INT _ 0; inDirectory: BOOL _ FALSE; inType: BOOL _ FALSE; AddRef: PROC [newRef: LIST OF IdRef] = { tail: LIST OF IdRef _ lastId.refs; IF tail = NIL THEN { idList _ CONS[lastId, idList]; newRef.rest _ newRef; uniqueTokensRead _ uniqueTokensRead + 1; } ELSE { newRef.rest _ tail.rest; tail.rest _ newRef; }; tokensRead _ tokensRead + 1; lastId.refs _ newRef; }; curFileName: ROPE _ FS.GetName[FS.OpenFileFromStream[stream]].fullFName; IF NOT switches['f] THEN curFileName _ FileNames.GetShortName[curFileName]; ReportName[curFileName]; filesRead _ filesRead + 1; IF fileListTail = NIL THEN fileListTail _ fileListHead _ LIST[curFileName] ELSE fileListTail _ (fileListTail.rest _ LIST[curFileName]); IF symTab = NIL THEN InitTable[]; IF switches['t] THEN { start: INT; DO c: CHAR _ IO.GetChar[stream ! IO.EndOfStream => EXIT]; SELECT c FROM IN ['a..'z], IN ['A..'Z] => {}; ENDCASE => LOOP; cToken[0] _ c; cToken.length _ 1; start _ IO.GetIndex[stream] - 1; DO c: CHAR _ IO.GetChar[stream ! IO.EndOfStream => EXIT]; SELECT c FROM IN ['a..'z], IN ['A..'Z], IN ['0..'9], '' => cToken _ RefText.InlineAppendChar[cToken, c]; ENDCASE => EXIT; ENDLOOP; WITH SymTab.FetchText[symTab, cToken].val SELECT FROM id: REF IdEntry => lastId _ id; ENDCASE => { new: REF IdEntry _ NEW[IdEntry _ [name: Rope.FromRefText[cToken]]]; [] _ SymTab.Store[symTab, new.name, new]; lastId _ new; }; AddRef[LIST[[curFileName, start, FALSE]]]; ENDLOOP; } ELSE { DO tokenKind: IO.TokenKind; Process.CheckForAbort[]; [token: cToken, tokenKind: tokenKind] _ IO.GetCedarToken[stream, cToken]; SELECT tokenKind FROM tokenSINGLE => { SELECT cToken[0] FROM '[, '( => parenLevel _ parenLevel + 1; '], ') => parenLevel _ parenLevel - 1; '{ => IF NOT inType THEN blockLevel _ blockLevel + 1; '} => IF NOT inType THEN { c: CHAR _ '.; c _ IO.PeekChar[stream ! IO.EndOfStream => CONTINUE]; blockLevel _ blockLevel - 1; IF blockLevel <= 0 OR c = '. THEN EXIT; }; ', => LOOP; '; => GO TO clearFlags; ENDCASE; EXITS clearFlags => inDirectory _ inType _ FALSE; }; tokenID => { WITH SymTab.FetchText[symTab, cToken].val SELECT FROM id: REF IdEntry => { SELECT id.reserved FROM NIL => { lastId _ id; GO TO addReference; }; $BEGIN => blockLevel _ blockLevel + 1; $END => { c: CHAR _ '.; c _ IO.PeekChar[stream ! IO.EndOfStream => CONTINUE]; blockLevel _ blockLevel - 1; IF blockLevel <= 0 OR c = '. THEN EXIT; }; $DIRECTORY => {inDirectory _ TRUE; GO TO retainDirectoryFlag}; $USING => GO TO retainDirectoryFlag; $TYPE => {inType _ TRUE; GO TO retainDirectoryFlag}; ENDCASE; inDirectory _ FALSE; EXITS retainDirectoryFlag => {}; }; ENDCASE => { new: REF IdEntry _ NEW[IdEntry _ [name: Rope.FromRefText[cToken]]]; [] _ SymTab.Store[symTab, new.name, new]; lastId _ new; GO TO addReference; }; EXITS addReference => { ENABLE IO.EndOfStream => LOOP; index: INT _ IO.GetIndex[stream]-cToken.length; defn: BOOL _ FALSE; c: CHAR _ 0C; [] _ IO.SkipWhitespace[stream ! IO.EndOfStream => CONTINUE]; c _ IO.PeekChar[stream ! IO.EndOfStream => CONTINUE]; IF c = ': THEN IF parenLevel = 0 THEN defn _ TRUE; AddRef[LIST[[curFileName, index, defn]]]; LOOP; }; }; tokenERROR => EXIT; tokenEOF => EXIT; ENDCASE; lastId _ NIL; ENDLOOP; }; IO.Close[stream]; }; InitTable: PROC = { reserved: ROPE _ "RECORD POINTER REF VAR LIST ARRAY SEQUENCE DESCRIPTOR PROCEDURE PROC PORT SIGNAL ERROR PROCESS PROGRAM MONITOR DEFINITIONS ZONE RELATIVE LONG TYPE FRAME TO ORDERED UNCOUNTED PAINTED BASE OF PACKED RETURNS SAFE UNSAFE MONITORED OVERLAID COMPUTED MACHINE DEPENDENT DIRECTORY IMPORTS EXPORTS SHARES LOCKS USING PUBLIC PRIVATE CEDAR CHECKED TRUSTED UNCHECKED ENTRY INTERNAL INLINE READONLY CODE ABS ALL AND APPLY CONS MAX MIN MOD NOT OR ORD PRED LENGTH NEW START SUCC VAL FORK JOIN LOOPHOLE NARROW ISTYPE SIZE FIRST LAST NIL TRASH NULL IF THEN ELSE WITH FROM FOR DECREASING IN THROUGH UNTIL WHILE REPEAT FINISHED RETURN EXIT LOOP GOTO GO FREE WAIT RESTART NOTIFY BROADCAST STOP RESUME REJECT CONTINUE RETRY TRANSFER STATE OPEN ENABLE ANY EXITS END ENDLOOP ENDCASE BEGIN DO SELECT "; builtIn: ROPE _ "TRUE FALSE INT CARD INTEGER CARDINAL BOOL BOOLEAN UNSPECIFIED CHARACTER CHAR WORD REAL NAT TEXT STRING MONITORLOCK CONDITION MDSZone StringBody ATOM UNWIND ABORTED "; symTab _ SymTab.Create[1253]; AddReserved[symTab, reserved]; AddReserved[symTab, builtIn]; }; ReportName: PROC [name: ROPE] = { THROUGH [0..indent) DO IO.PutChar[out, ' ]; ENDLOOP; IO.PutF1[out, " reading %g\n", [rope[name]]]; }; DumpXref: PROC [arg: ROPE] = { tc: TiogaAccess.TiogaChar _ [ charSet: 0, char: '\n, looks: ALL[FALSE], format: NIL, comment: FALSE, endOfNode: TRUE, deltaLevel: 1, propList: List.PutAssoc[key: $FromTiogaFile, val: $Yes, aList: NIL] ]; PutRope: PROC [rope: ROPE] = { [] _ Rope.Map[base: rope, action: PutCharB]; }; PutRopeBold: PROC [rope: ROPE] = { tc.looks['n] _ TRUE; [] _ Rope.Map[base: rope, action: PutCharB]; tc.looks['n] _ FALSE; }; PutRopeItalic: PROC [rope: ROPE] = { tc.looks['c] _ TRUE; [] _ Rope.Map[base: rope, action: PutCharB]; tc.looks['c] _ FALSE; }; PutCharB: Rope.ActionType = { tc.char _ c; TiogaAccess.Put[writer, tc]; }; PutChar: PROC [c: CHAR] = { tc.char _ c; TiogaAccess.Put[writer, tc]; }; EndNode: PROC [delta: INTEGER _ 0, format: ATOM _ NIL] = { tc.endOfNode _ TRUE; tc.char _ '\n; tc.format _ format; tc.deltaLevel _ delta; TiogaAccess.Put[writer, tc]; tc.endOfNode _ FALSE; }; writer: TiogaAccess.Writer _ TiogaAccess.Create[]; buffer: REF TEXT _ NEW[TEXT[12]]; IF filesRead = 1 THEN IO.PutRope[out, " 1 file read\n"] ELSE IO.PutF1[out, " %g files read\n", [integer[filesRead]] ]; TiogaAccess.Put[writer, tc]; tc.comment _ TRUE; tc.endOfNode _ FALSE; tc.propList _ NIL; PutRope[arg]; EndNode[1]; PutRope[IO.PutFR1["taken on %g", [time[BasicTime.Now[]]]]]; EndNode[0]; PutRope[IO.PutFR["# files: %g, # tokens: %g, # unique tokens: %g", [integer[filesRead]], [integer[tokensRead]], [integer[uniqueTokensRead]] ]]; EndNode[1]; FOR each: LIST OF ROPE _ fileListHead, each.rest WHILE each # NIL DO PutRope[each.first]; EndNode[0]; ENDLOOP; EndNode[-2]; tc.comment _ FALSE; idList _ List.Sort[idList, MySort]; WHILE idList # NIL DO each: REF IdEntry _ NARROW[idList.first]; tail: LIST OF IdRef _ each.refs; IF tail # NIL THEN { refs: LIST OF IdRef _ tail.rest; fileName: ROPE _ NIL; tail.rest _ NIL; each.refs _ NIL; PutRopeBold[each.name]; PutRopeBold[":"]; IF filesRead > 1 THEN EndNode[1, $code]; WHILE refs # NIL DO index: INT = refs.first.index; tail _ refs.rest; IF filesRead > 1 AND refs.first.fileName # fileName THEN { IF fileName # NIL THEN EndNode[0, $code]; fileName _ refs.first.fileName; PutRopeItalic[fileName]; PutChar[' ]; }; buffer.length _ 0; [] _ Convert.AppendInt[buffer, ABS[index], 10, FALSE]; PutChar[' ]; IF refs.first.defn THEN PutRopeBold[RefText.TrustTextAsRope[buffer]] ELSE PutRope[RefText.TrustTextAsRope[buffer]]; refs.rest _ NIL; refs _ tail; ENDLOOP; EndNode[IF filesRead > 1 THEN -1 ELSE 0, $code]; }; idList _ idList.rest; ENDLOOP; IO.PutF1[out, " writing to %g\n", [rope[arg]]]; TiogaAccess.WriteFile[writer, arg]; symTab _ NIL; xrefName _ NIL; filesRead _ 0; }; indent: INTEGER _ 0; symTab: SymTab.Ref _ NIL; idList: LIST OF REF ANY _ NIL; cToken: REF TEXT _ NEW[TEXT[128]]; xrefName: ROPE _ NIL; filesRead: INT _ 0; tokensRead: INT _ 0; uniqueTokensRead: INT _ 0; fileListHead: LIST OF ROPE _ NIL; fileListTail: LIST OF ROPE _ NIL; argv: CommandTool.ArgumentVector _ CommandTool.Parse[cmd: cmd, starExpand: FALSE ! CommandTool.Failed => {msg _ errorMsg; GO TO failed}]; FOR i: NAT IN [1..argv.argc) DO arg: ROPE _ argv[i]; Process.CheckForAbort[]; SELECT TRUE FROM Rope.Length[arg] = 0 => {}; Rope.Fetch[arg, 0] = '- => ProcessSwitches[arg]; ENDCASE => { { ENABLE { FS.Error => {msg _ error.explanation; GO TO cleanUp}; DFUtilities.SyntaxError => {msg _ reason; GO TO cleanUp}; }; EachFile: DFUtilities.ProcessItemProc = { WITH item SELECT FROM fileItem: REF DFUtilities.FileItem => SELECT TRUE FROM Rope.Match["*.mesa!*", fileItem.name, FALSE], Rope.Match["*.mesa", fileItem.name, FALSE] => { sName: ROPE = FileNames.GetShortName[fileItem.name]; name: ROPE _ sName; date: BasicTime.GMT _ BasicTime.nullGMT; st: STREAM _ NIL; IF NOT switches['h] THEN { name _ Rope.Concat[path, sName]; date _ fileItem.date.gmt; }; st _ FS.StreamOpen[ fileName: name, wantedCreatedTime: date, remoteCheck: FALSE ! FS.Error => IF error.code = $unknownFile AND switches['h] THEN CONTINUE]; IF st = NIL THEN { name _ Rope.Concat[path, sName]; st _ FS.StreamOpen[fileName: name]; }; EachStream[st]; }; ENDCASE; dirItem: REF DFUtilities.DirectoryItem => path _ dirItem.path1; inclItem: REF DFUtilities.IncludeItem => EachDF[inclItem.path1, inclItem.date.gmt]; ENDCASE; }; EachDF: PROC [dfName: ROPE, date: BasicTime.GMT _ BasicTime.nullGMT] = { inclStream: STREAM _ NIL; sName: ROPE = FileNames.GetShortName[dfName]; ReportName[sName]; inclStream _ FS.StreamOpen[fileName: dfName, wantedCreatedTime: date]; indent _ indent + 2; DFUtilities.ParseFromStream[ inclStream, EachFile, [filterA: source, filterC: defining] ! UNWIND => IO.Close[inclStream];]; IO.Close[inclStream]; indent _ indent - 2; }; EachName: FS.NameProc = { name: ROPE _ NIL; continue _ TRUE; IF xrefName = NIL THEN { sName: ROPE _ FileNames.GetShortName[fullFName]; pos: INT _ Rope.Length[sName]; xrefName _ Rope.Flatten[sName]; WHILE pos > 0 DO SELECT Rope.Fetch[xrefName, pos-1] FROM '! => xrefName _ Rope.Flatten[xrefName, 0, pos-1]; '. => {xrefName _ Rope.Flatten[xrefName, 0, pos-1]; EXIT}; ENDCASE; pos _ pos - 1; ENDLOOP; xrefName _ Rope.Concat[xrefName, ".xref"]; }; SELECT TRUE FROM (name _ FileNames.FileWithSearchRules[root: fullFName, defaultExtension: ".mesa", searchRules: NIL].fullPath) # NIL => EachStream[FS.StreamOpen[name]]; (name _ FileNames.FileWithSearchRules[root: fullFName, defaultExtension: ".tioga", searchRules: NIL].fullPath) # NIL => EachStream[FS.StreamOpen[name]]; (name _ FileNames.FileWithSearchRules[root: fullFName, defaultExtension: ".df", searchRules: NIL].fullPath) # NIL => IF switches['s] THEN EachStream[FS.StreamOpen[name]] ELSE EachDF[name]; switches['t] => EachStream[FS.StreamOpen[fullFName]]; ENDCASE => IO.PutF1[out, "-- Warning: %g was not found.\n", [rope[fullFName]] ]; }; path: ROPE _ NIL; IF Rope.SkipTo[arg, 0, "*"] # Rope.Length[arg] THEN { IF Rope.SkipTo[arg, 0, "!"] = Rope.Length[arg] THEN arg _ Rope.Concat[arg, "!h"]; FS.EnumerateForNames[arg, EachName]; } ELSE [] _ EachName[arg]; EXITS cleanUp => { FOR each: LIST OF REF ANY _ idList, each.rest WHILE each # NIL DO WITH each.first SELECT FROM entry: REF IdEntry => { ring: LIST OF IdRef _ entry.refs; entry.refs _ NIL; IF ring # NIL THEN ring.rest _ NIL; }; ENDCASE; ENDLOOP; symTab _ NIL; GO TO failed; }; }; }; ENDLOOP; IF filesRead # 0 THEN DumpXref[xrefName]; EXITS failed => {result _ $Failure}; }; AddReserved: PROC [symTab: SymTab.Ref, rope: ROPE] = { pos: INT _ 0; len: INT _ Rope.Length[rope]; DO next: INT _ Rope.SkipTo[rope, pos, " "]; flat: ROPE _ Rope.Flatten[rope, pos, next-pos]; new: REF IdEntry _ NEW[IdEntry _ [name: flat, reserved: Atom.MakeAtom[flat]]]; [] _ SymTab.Store[symTab, flat, new]; pos _ Rope.SkipOver[rope, next, " "]; IF pos = len THEN EXIT; ENDLOOP; }; MySort: List.CompareProc = { name1: ROPE = NARROW[ref1, REF IdEntry].name; name2: ROPE = NARROW[ref2, REF IdEntry].name; IF name1 = name2 THEN RETURN [equal]; SELECT Rope.Compare[name1, name2, FALSE] FROM less => RETURN [less]; greater => RETURN [greater]; ENDCASE => RETURN [Rope.Compare[name1, name2, TRUE]]; }; doc: ROPE = "takes cross-references of Cedar source files listed in the given DF files -f: print full file names -h: use local highest versions of files -s: shallow, don't follow df files -t: text file, not Cedar tokens"; Commander.Register[ key: "///Commands/XRef", proc: DoCommandProc, doc: doc, clientData: NIL, interpreted: TRUE ]; END. fXRef.mesa Copyright c 1986 by Xerox Corporation. All rights reserved. Russ Atkinson (RRA) September 30, 1986 3:46:41 pm PDT [cmd: Handle] RETURNS [result: REF _ NIL, msg: ROPE _ NIL] CommandObject = [in, out, err: STREAM, commandLine, command: ROPE, ...] Takes an open stream and adds the info to the xref. text, not Cedar/Mesa Could be a reference Cedar/Mesa reserved words derived from Pass1T.mesa Cedar/Mesa built-in types derived from Pass1.mesa Now we can put out the cross-reference [c: CHAR] RETURNS [quit: BOOL _ FALSE] When parsing the command line, be prepared for failure. The error is reported to the user Each argument can either be a switch specification or a genuine argument to be processed. The first argument (argv[0]) is not examined, because by convention it is the name of the command as given by the user. It is a good idea to periodically check for a process abort request. [item: REF ANY] RETURNS [stop: BOOL _ FALSE] The local open failed because there was no local version, so try the remote version (with the highest version number). [fullFName: ROPE] RETURNS [continue: BOOL] A pattern to enumerate Appears to be a single file The idea is to charge down the list, breaking the circularities as we go. [ref1: REF ANY, ref2: REF ANY] RETURNS [Basics.Comparison] key is the name of the command. Registering the command under the "///Commands/" directory indicates that this command should be globally known and useful. If no directory is given, the command is registered under the current working directory, which is the proper place for more specific commands. proc is called to execute the command. Êò˜codešœ ™ Kšœ Ïmœ1™K˜——šÏnœžœž˜KšžœIžœžœ3˜‹Kšœž˜—˜Kšžœžœžœ˜Kšžœžœžœžœ˜K˜šœ žœžœ˜Kšœžœ˜ Kšœžœžœ žœ˜Kšœ žœžœ˜K˜—šœžœžœ˜Kšœ žœÏc#˜3Kšœžœ ˜,Kšœžœ !˜.——K˜šŸ œ˜(š œžœ žœžœžœžœ™:Kšœžœžœ™G—Kšœžœ ˜Kšœ žœžœžœ žœžœžœžœ˜9šŸœžœžœ˜%Kšœžœžœ˜šžœžœžœž˜*Kšœžœ˜$šžœž˜Kšœžœ˜ Kšœžœžœ˜ Kšœ žœžœ˜3Kšžœ$˜&Kšžœ.˜0Kšžœ˜—Kšœžœ˜ Kšžœ˜—K˜—šŸ œžœ žœ˜%Kšœ3™3Kšœ žœ˜Kšœ žœ˜Kšœžœ žœ˜Kšœ žœ˜Kšœ žœžœ˜Kšœžœžœ˜šŸœžœ žœžœ ˜(Kšœžœžœ˜"šžœž˜ šžœ˜Kšœ žœ˜Kšœ˜Kšœ(˜(K˜—šžœ˜Kšœ˜Kšœ˜K˜——Kšœ˜Kšœ˜K˜—Kšœ žœžœ žœ'˜HKšžœžœžœ3˜KKšœ˜K˜šžœž˜Kšžœžœ ˜4Kšžœ%žœ˜<—Kšžœ žœžœ ˜!šžœ ˜šžœ˜K™Kšœžœ˜ šž˜Kš œžœžœžœžœ˜6šžœž˜ Kšžœ žœ˜Kšžœžœ˜—K˜K˜Kšœžœ˜ šž˜Kš œžœžœžœžœ˜6šžœž˜ šžœ žœ žœ˜,Kšœ-˜-—Kšžœžœ˜—Kšžœ˜—šžœ&žœž˜5Kšœžœ˜šžœ˜ Kšœžœ žœ-˜CKšœ)˜)Kšœ ˜ K˜——Kšœžœžœ˜*Kšžœ˜—K˜—šžœ˜šž˜Kšœ žœ ˜K˜Kšœ(žœ˜Išžœ ž˜šœ˜šžœ ž˜Kšœ&˜&Kšœ&˜&Kšœžœžœžœ˜5šœžœžœžœ˜Kšœžœ˜ Kšœžœžœžœ˜5Kšœ˜Kšžœžœžœžœ˜'K˜—Kšœžœ˜ Kšœžœžœ ˜Kšžœ˜—šž˜˜ Kšœžœ˜——K˜—šœ ˜ šžœ&žœž˜5šœžœ ˜šžœ ž˜šžœ˜Kšœ ˜ Kšžœžœ˜K˜—Kšœ&˜&šœ ˜ Kšœžœ˜ Kšœžœžœžœ˜5Kšœ˜Kšžœžœžœžœ˜'K˜—Kšœžœžœžœ˜>Kšœ žœžœ˜$Kšœžœžœžœ˜4Kšžœ˜—Kšœžœ˜Kšžœ˜ K˜—šžœ˜ Kšœžœ žœ-˜CKšœ)˜)Kšœ ˜ Kšžœžœ˜K˜——šžœ˜Kšžœžœžœ˜Kšœžœžœ ˜/Kšœžœžœ˜Kšœžœ˜ Kšœžœžœžœ˜ -- [ref1: REF ANY, ref2: REF ANY] RETURNS [Basics.Comparison]šŸœ˜Kš¡:™:Kšœžœžœžœ˜-Kšœžœžœžœ˜-Kšžœžœžœ ˜%šžœžœž˜-Kšœžœ˜Kšœ žœ ˜Kšžœžœžœ˜5—K˜K˜—Kšœžœä˜íK˜–x[key: ROPE, proc: Commander.CommandProc, doc: ROPE _ NIL, clientData: REF ANY _ NIL, interpreted: BOOL _ TRUE]šœ˜šœ˜KšÏoœ©™¬—šœ˜Kš¢œ"™&—Kšœ ˜ Kšœ žœ˜Kšœ ž˜Kšœ˜—K˜Kšžœ˜K˜šœ˜K™——…—6<S”