DIRECTORY Ascii USING [CR, LF, SP, TAB], BasicTime USING [daysPerMonth, hoursPerDay, minutesPerHour, MonthOfYear, OutOfRange, Pack, secondsPerMinute, TimeParametersNotKnown, Unpack, Unpacked, unspecifiedZone, Zone], DFUtilities USING [CommentItem, Date, DirectoryItem, FileItem, Filter, FilterA, FilterB, FilterC, ImportsItem, IncludeItem, ProcessItemProc, SupplyItemProc, UsingEntry, UsingForm, UsingList, WhiteSpaceItem], IO USING [Backup, BreakProc, EndOfStream, Error, GetChar, GetLineRope, GetToken, PeekChar, PutChar, PutFLR, PutFR, PutRope, STREAM, TokenProc], RefText USING [InlineAppendChar, Equal, Fetch, Length, ObtainScratch, ReleaseScratch, TrustTextAsRope], Rope USING [Compare, Concat, Equal, Fetch, Find, FindBackward, FromRefText, Length, ROPE, Substr]; DFUtilitiesImpl: CEDAR PROGRAM IMPORTS BasicTime, IO, RefText, Rope EXPORTS DFUtilities = BEGIN ROPE: TYPE = Rope.ROPE; STREAM: TYPE = IO.STREAM; LineSep: CHAR ~ '\n; LineSepText: REF TEXT ~ "\r"; IsLineSep: PROC [c: CHAR] RETURNS [BOOL] ~ INLINE { RETURN [(c = Ascii.LF) OR (c = Ascii.CR)]; }; ParseFromStream: PUBLIC PROC [in: STREAM, proc: DFUtilities.ProcessItemProc, filter: DFUtilities.Filter ฌ []] = { Abort: ERROR = CODE; ParseInner: PROC = { passFileItems: BOOL ฌ TRUE; underDirectory: BOOL ฌ FALSE; blankLineCount: NAT ฌ 0; readFakeNewline: BOOL ฌ FALSE; bufferT: REF TEXT = RefText.ObtainScratch[20]; bufferN: REF TEXT = RefText.ObtainScratch[100]; SimpleToken: IO.BreakProc -- [char: CHAR] RETURNS [IO.CharClass] -- = { SELECT char FROM Ascii.SP, Ascii.TAB => RETURN [$sepr]; Ascii.CR, Ascii.LF => RETURN [$break]; ENDCASE => RETURN [$other]; }; PreUsingToken: IO.BreakProc -- [char: CHAR] RETURNS [IO.CharClass] -- = { SELECT char FROM Ascii.SP, Ascii.TAB => RETURN [$sepr]; Ascii.CR, Ascii.LF, '[ => RETURN [$break]; ENDCASE => RETURN [$other]; }; UsingToken: IO.BreakProc -- [char: CHAR] RETURNS [IO.CharClass] -- = { SELECT char FROM '-, '_ => RETURN [$other]; ENDCASE => RETURN [IO.TokenProc[char]]; }; GetTokenAsRefText: PROC [breakProc: IO.BreakProc] RETURNS [token: REF TEXT] = INLINE {RETURN FullGetToken[breakProc, bufferT]}; GetToken: PROC [breakProc: IO.BreakProc] RETURNS [ROPE] = INLINE {RETURN [Rope.FromRefText[FullGetToken[breakProc, bufferN]]]}; FullGetToken: PROC [breakProc: IO.BreakProc, buffer: REF TEXT] RETURNS [token: REF TEXT] ~ { IF breakProc = SimpleToken THEN {char: CHAR; DO char ฌ in.GetChar[!IO.EndOfStream => GOTO EOF]; SELECT char FROM Ascii.SP, Ascii.TAB => NULL; ENDCASE => EXIT; ENDLOOP; IF char = '[ THEN { state: {server, startDir, finishDir, tail} ฌ server; buffer.length ฌ 1; buffer[0] ฌ char; DO char ฌ in.GetChar[!IO.EndOfStream => GOTO EOF]; IF IsLineSep[char] THEN EXIT; SELECT state FROM server => IF char='] THEN state ฌ startDir; startDir => SELECT char FROM '< => state ฌ finishDir; Ascii.SP, Ascii.TAB => EXIT; ENDCASE => state ฌ tail; finishDir => IF char='> THEN state ฌ tail; tail => SELECT char FROM Ascii.SP, Ascii.TAB => EXIT; ENDCASE => NULL; ENDCASE => ERROR; buffer ฌ RefText.InlineAppendChar[buffer, char]; ENDLOOP; in.Backup[char]; RETURN [buffer]; } ELSE in.Backup[char]; }; token ฌ in.GetToken[breakProc, buffer !IO.EndOfStream => GOTO EOF].token; RETURN; EXITS EOF => {readFakeNewline ฌ TRUE; token ฌ LineSepText}}; PutBack: PROC [x: REF TEXT] = { IF readFakeNewline THEN { IF NOT RefText.Equal[x, LineSepText] THEN ERROR SyntaxError["Internal bug."]; } ELSE FOR i: INT DECREASING IN [0..RefText.Length[x]) DO in.Backup[RefText.Fetch[x, i]]; ENDLOOP; }; EndOfLineText: PROC [t: REF TEXT] RETURNS [BOOL] = { RETURN [RefText.Length[t] = 1 AND IsLineSep[RefText.Fetch[t, 0]]] }; EndOfLineRope: PROC [t: ROPE] RETURNS [BOOL] = { RETURN [t.Length[] = 1 AND IsLineSep[t.Fetch[0]]] }; CheckEndOfLine: PROC = { IF ~EndOfLineText[GetTokenAsRefText[SimpleToken]] THEN ERROR SyntaxError["Unrecognizable text where end-of-line was expected."]; }; FlushWhiteSpace: PROC = { IF blankLineCount ~= 0 AND filter.comments THEN { IF proc[NEW[DFUtilities.WhiteSpaceItem ฌ [lines: blankLineCount]]] THEN ERROR Abort; CancelWhiteSpace[]; }; }; CancelWhiteSpace: PROC = {blankLineCount ฌ 0}; CancelDirectory: PROC = {underDirectory ฌ FALSE}; ParseDirectoryItem: PROC [exported: BOOL, readOnly: BOOL, path1: ROPE ฌ NIL] RETURNS [REF DFUtilities.DirectoryItem ฌ NIL] = { directoryFilterB: DFUtilities.FilterB = IF exported THEN $public ELSE $private; directoryFilterC: DFUtilities.FilterC = IF readOnly THEN $imported ELSE $defining; ConsiderDefiningInstance: PROC RETURNS [BOOL] = { RETURN [ (filter.filterB = $all OR filter.filterB = directoryFilterB) AND (filter.filterC = $all OR filter.filterC = directoryFilterC) ]; }; path2: ROPE ฌ NIL; path2IsCameFrom: BOOL ฌ FALSE; x: REF TEXT; IF path1 = NIL AND EndOfLineRope[path1 ฌ GetToken[SimpleToken]] THEN ERROR SyntaxError["Missing directory path."]; IF ~EndOfLineText[x ฌ GetTokenAsRefText[SimpleToken]] THEN { SELECT TRUE FROM RefText.Equal[x, "ReleaseAs", FALSE] => path2IsCameFrom ฌ FALSE; RefText.Equal[x, "CameFrom", FALSE] => path2IsCameFrom ฌ TRUE; ENDCASE => ERROR SyntaxError["Unrecognized construct following directory path."]; IF EndOfLineRope[path2 ฌ GetToken[SimpleToken]] THEN RaiseSyntaxError["Missing directory path following", x]; CheckEndOfLine[]; }; underDirectory ฌ TRUE; IF path1.Find["/"]#-1 OR path2.Find["/"]#-1 THEN ERROR SyntaxError["Slash found in directory; use bracket instead."]; RETURN [ IF (passFileItems ฌ ConsiderDefiningInstance[]) THEN NEW[DFUtilities.DirectoryItem ฌ [ path1: path1, path2: path2, path2IsCameFrom: path2IsCameFrom, exported: exported, readOnly: readOnly ]] ELSE NIL ] }; ParseFileItem: PROC [verifyRoot: BOOL ฌ FALSE, name: ROPE ฌ NIL] RETURNS [REF DFUtilities.FileItem] = { PassesNameFilter: PROC [file: ROPE] RETURNS [BOOL] = { SELECT TRUE FROM filter.list # NIL => {}; filter.filterA = $all => {}; ClassifyFileExtension[file] = filter.filterA => {}; ENDCASE => RETURN [FALSE]; RETURN [SearchUsingList[file, filter.list].found] }; date: DFUtilities.Date; IF ~underDirectory THEN ERROR SyntaxError["Missing directory statement"]; IF name = NIL AND EndOfLineRope[name ฌ GetToken[SimpleToken]] THEN ERROR SyntaxError["Missing file name."]; date ฌ GetDateAndLineSep[in, bufferN]; IF name.Find["/"]#-1 THEN ERROR SyntaxError["Slash found in file name; use bracket instead."]; RETURN [ IF passFileItems AND PassesNameFilter[RemoveVersionNumber[name]] THEN NEW[DFUtilities.FileItem ฌ [ name: name, date: date, verifyRoot: verifyRoot ]] ELSE NIL ] }; ParseImportsItem: PROC [exported: BOOL] RETURNS [REF DFUtilities.ImportsItem] = { x: REF TEXT; path1, path2: ROPE ฌ NIL; date: DFUtilities.Date; form: DFUtilities.UsingForm ฌ $exports; list: REF DFUtilities.UsingList ฌ NIL; ConsiderImports: PROC RETURNS [BOOL] = { IF filter.filterC = $defining THEN RETURN [FALSE]; IF exported AND filter.list # NIL THEN { RETURN [form = $all OR form = $exports OR list # NIL]; }; SELECT filter.filterB FROM $private => IF exported OR form = $exports THEN RETURN [FALSE]; $public => IF ~exported THEN RETURN [FALSE]; ENDCASE; RETURN [~(form = $list AND list = NIL)] }; IF EndOfLineRope[path1 ฌ GetToken[SimpleToken]] THEN ERROR SyntaxError["Missing file name."]; IF EndOfLineText[x ฌ GetTokenAsRefText[SimpleToken]] OR ~RefText.Equal[x, "Of", FALSE] THEN ERROR SyntaxError["Missing 'Of' following DF name"]; date ฌ GetDateAndLineSep[in, bufferN]; x ฌ GetTokenAsRefText[PreUsingToken]; IF RefText.Equal[x, "CameFrom", FALSE] THEN { IF EndOfLineRope[path2 ฌ GetToken[SimpleToken]] THEN RaiseSyntaxError["Missing directory path following", x]; CheckEndOfLine[]; x ฌ GetTokenAsRefText[PreUsingToken]; }; IF RefText.Equal[x, "Using", FALSE] THEN { x ฌ GetTokenAsRefText[UsingToken]; SELECT TRUE FROM RefText.Equal[x, "All", FALSE] => form ฌ $all; RefText.Equal[x, "Exports", FALSE] => --form ฌ $exports-- NULL; RefText.Equal[x, "[", FALSE] => { verifyRoot: BOOL ฌ FALSE; form ฌ $list; DO index: NAT; inList: BOOL; x ฌ GetTokenAsRefText[UsingToken]; IF RefText.Length[x] = 1 THEN SELECT RefText.Fetch[x, 0] FROM '] => EXIT; '+ => IF ~verifyRoot THEN {verifyRoot ฌ TRUE; LOOP} ELSE ERROR SyntaxError["Illegally placed '+' in 'Using' list."]; ENDCASE; IF RefText.TrustTextAsRope[x].Find["/"]#-1 THEN ERROR SyntaxError["Slash found in file name; use bracket instead."]; [inList, index] ฌ SearchUsingList[RefText.TrustTextAsRope[x], filter.list]; IF inList THEN { SELECT TRUE FROM list = NIL => { length: NAT = IF filter.list = NIL THEN 20 ELSE filter.list.nEntries; list ฌ NEW[DFUtilities.UsingList[length]]; list.nEntries ฌ 0; }; list.nEntries = list.length => { newList: REF DFUtilities.UsingList ฌ NEW[DFUtilities.UsingList[(list.length*3)/2]]; newList.nEntries ฌ list.nEntries; FOR i: NAT IN [0..list.nEntries) DO newList.u[i] ฌ list.u[i]; ENDLOOP; list ฌ newList; }; ENDCASE; list.u[list.nEntries] ฌ [ verifyRoot: verifyRoot, name: IF filter.list = NIL THEN Rope.FromRefText[x] ELSE filter.list.u[index].name ]; list.nEntries ฌ list.nEntries.SUCC; }; verifyRoot ฌ FALSE; ENDLOOP; }; ENDCASE => ERROR SyntaxError["Unrecognized construct following 'Using'."]; CheckEndOfLine[]; } ELSE { PutBack[x]; }; CancelDirectory[]; IF path1.Find["/"]#-1 THEN ERROR SyntaxError["Slash found in DF name; use bracket instead."]; IF path2.Find["/"]#-1 THEN ERROR SyntaxError["Slash found in directory; use bracket instead."]; RETURN [ IF ConsiderImports[] THEN NEW[DFUtilities.ImportsItem ฌ [ path1: path1, date: date, path2: path2, exported: exported, form: form, list: list ]] ELSE NIL ] }; ParseIncludeItem: PROC RETURNS [REF DFUtilities.IncludeItem] = { item: REF DFUtilities.IncludeItem = NEW[DFUtilities.IncludeItem ฌ [ path1: GetToken[SimpleToken], date: , path2: NIL, path2IsCameFrom: ]]; x: REF TEXT; IF EndOfLineRope[item.path1] THEN ERROR SyntaxError["Missing file name."]; IF EndOfLineText[x ฌ GetTokenAsRefText[SimpleToken]] OR ~RefText.Equal[x, "Of", FALSE] THEN ERROR SyntaxError["Missing 'Of' following DF name"]; item.date ฌ GetDateAndLineSep[in, bufferN]; x ฌ GetTokenAsRefText[SimpleToken]; SELECT TRUE FROM RefText.Equal[x, "ReleaseAs", FALSE] => item.path2IsCameFrom ฌ FALSE; RefText.Equal[x, "CameFrom", FALSE] => item.path2IsCameFrom ฌ TRUE; ENDCASE => {PutBack[x]; RETURN [item]}; IF EndOfLineRope[item.path2 ฌ GetToken[SimpleToken]] THEN RaiseSyntaxError["Missing directory path following", x]; CheckEndOfLine[]; CancelDirectory[]; IF item.path1.Find["/"]#-1 THEN ERROR SyntaxError["Slash found in DF name; use bracket instead."]; IF item.path2.Find["/"]#-1 THEN ERROR SyntaxError["Slash found in directory; use bracket instead."]; RETURN [item] }; DO item: REF ANY ฌ NIL; char: CHAR = in.GetChar[ ! IO.EndOfStream => EXIT]; SELECT char FROM Ascii.SP, Ascii.TAB => LOOP; -- leading white space on line '* => LOOP; -- a flag used in XDE DF files, similar to '+, we ignore it Ascii.CR, Ascii.LF => { IF filter.comments THEN blankLineCount ฌ blankLineCount.SUCC; LOOP }; '+ => { FlushWhiteSpace[]; item ฌ ParseFileItem[verifyRoot: TRUE]; }; '- => { FlushWhiteSpace[]; IF in.PeekChar[] = '- THEN { comment: ROPE = Rope.Concat["-", in.GetLineRope[]]; IF filter.comments THEN item ฌ NEW[DFUtilities.CommentItem ฌ [text: comment]]; } ELSE {in.Backup[char]; item ฌ ParseFileItem[]}; }; '/ => { FlushWhiteSpace[]; IF in.GetChar[] = '/ THEN { comment: ROPE ฌ NIL; comment ฌ in.GetLineRope[ ! IO.EndOfStream => CONTINUE]; IF filter.comments THEN item ฌ NEW[DFUtilities.CommentItem ฌ [text: Rope.Concat["--", comment]]]; } ELSE ERROR SyntaxError["'/' is illegal at the start of a line."]; }; ENDCASE => { x: REF TEXT; in.Backup[char]; x ฌ GetTokenAsRefText[SimpleToken]; SELECT TRUE FROM RefText.Equal[x, "Directory", FALSE] => item ฌ ParseDirectoryItem[exported: FALSE, readOnly: FALSE]; RefText.Equal[x, "ReadOnly", FALSE] => item ฌ ParseDirectoryItem[exported: FALSE, readOnly: TRUE]; RefText.Equal[x, "Imports", FALSE] => item ฌ ParseImportsItem[exported: FALSE]; RefText.Equal[x, "Include", FALSE], RefText.Equal[x, "Includes", FALSE] => item ฌ ParseIncludeItem[]; RefText.Equal[x, "Exports", FALSE] => { IF EndOfLineText[x ฌ GetTokenAsRefText[SimpleToken]] THEN ERROR SyntaxError["Missing directory path following 'Exports'."]; SELECT TRUE FROM RefText.Equal[x, "Directory", FALSE] => item ฌ ParseDirectoryItem[exported: TRUE, readOnly: FALSE]; RefText.Equal[x, "ReadOnly", FALSE] => item ฌ ParseDirectoryItem[exported: TRUE, readOnly: TRUE]; RefText.Equal[x, "Imports", FALSE] => item ฌ ParseImportsItem[exported: TRUE]; ENDCASE => item ฌ ParseDirectoryItem[ exported: TRUE, readOnly: FALSE, path1: Rope.FromRefText[x]]; }; ENDCASE => item ฌ ParseFileItem[name: Rope.FromRefText[x]]; }; IF item ~= NIL THEN { FlushWhiteSpace[]; IF proc[item] THEN ERROR Abort; } ELSE CancelWhiteSpace[]; ENDLOOP; RefText.ReleaseScratch[bufferN]; RefText.ReleaseScratch[bufferT]; }; SortUsingList[usingList: filter.list, nearlySorted: TRUE]; ParseInner[ ! Abort => CONTINUE; IO.EndOfStream => ERROR SyntaxError["Unexpected end of DF."]; IO.Error => ERROR SyntaxError[NIL]; ] }; ParseDigits: PROC [text: REF TEXT, start, stop: NAT] RETURNS [n: INT ฌ 0] ~ { FOR i: NAT IN[start..stop) DO char: CHAR ~ text[i]; IF char IN['0..'9] THEN n ฌ n*10+(char-'0) ELSE EXIT; ENDLOOP; }; ParseMonth: PROC [text: REF TEXT, start, stop: NAT] RETURNS [BasicTime.MonthOfYear] ~ { State: TYPE ~ {null, A, Ap, Apr, Au, Aug, D, De, Dec, F, Fe, Feb, J, Ja, Jan, Ju, Jul, Jun, M, Ma, Mar, May, N, No, Nov, O, Oc, Oct, S, Se, Sep}; state: State ฌ null; FOR i: NAT IN[start..stop) DO char: CHAR ~ text[i]; SELECT state FROM null => SELECT char FROM 'A => state ฌ A; 'D => state ฌ D; 'F => state ฌ F; 'J => state ฌ J; 'M => state ฌ M; 'N => state ฌ N; 'O => state ฌ O; 'S => state ฌ S; ENDCASE => GOTO bogus; A => SELECT char FROM 'p => state ฌ Ap; 'u => state ฌ Au; ENDCASE => GOTO bogus; Ap => SELECT char FROM 'r => state ฌ Apr; ENDCASE => GOTO bogus; Au => SELECT char FROM 'g => state ฌ Aug; ENDCASE => GOTO bogus; D => SELECT char FROM 'e => state ฌ De; ENDCASE => GOTO bogus; De => SELECT char FROM 'c => state ฌ Dec; ENDCASE => GOTO bogus; F => SELECT char FROM 'e => state ฌ Fe; ENDCASE => GOTO bogus; Fe => SELECT char FROM 'b => state ฌ Feb; ENDCASE => GOTO bogus; J => SELECT char FROM 'a => state ฌ Ja; 'u => state ฌ Ju; ENDCASE => GOTO bogus; Ja => SELECT char FROM 'n => state ฌ Jan; ENDCASE => GOTO bogus; Ju => SELECT char FROM 'l => state ฌ Jul; 'n => state ฌ Jun; ENDCASE => GOTO bogus; M => SELECT char FROM 'a => state ฌ Ma; ENDCASE => GOTO bogus; Ma => SELECT char FROM 'r => state ฌ Mar; 'y => state ฌ May; ENDCASE => GOTO bogus; N => SELECT char FROM 'o => state ฌ No; ENDCASE => GOTO bogus; No => SELECT char FROM 'v => state ฌ Nov; ENDCASE => GOTO bogus; O => SELECT char FROM 'c => state ฌ Oc; ENDCASE => GOTO bogus; Oc => SELECT char FROM 't => state ฌ Oct; ENDCASE => GOTO bogus; S => SELECT char FROM 'e => state ฌ Se; ENDCASE => GOTO bogus; Se => SELECT char FROM 'p => state ฌ Sep; ENDCASE => GOTO bogus; ENDCASE => GOTO bogus; ENDLOOP; SELECT state FROM Jan => RETURN[January]; Feb => RETURN[February]; Mar => RETURN[March]; Apr => RETURN[April]; May => RETURN[May]; Jun => RETURN[June]; Jul => RETURN[July]; Aug => RETURN[August]; Sep => RETURN[September]; Oct => RETURN[October]; Nov => RETURN[November]; Dec => RETURN[December]; ENDCASE => GOTO bogus; EXITS bogus => RETURN [unspecified]; }; ZoneInfo: TYPE ~ RECORD [zone: BasicTime.Zone, dst: BOOL]; nullZoneInfo: ZoneInfo ~ [zone: BasicTime.unspecifiedZone, dst: FALSE]; ParseZone: PROC [text: REF TEXT, start, stop: NAT] RETURNS [info: ZoneInfo ฌ nullZoneInfo] ~ { minus, dst: BOOL ฌ FALSE; hrs, mins: INT ฌ 0; SELECT stop-start FROM 3 => { zoneRope: Rope.ROPE ฌ Rope.FromRefText[text, start, 3]; SELECT TRUE FROM -- GMT needs more general parser - MGL Rope.Equal[zoneRope, "GMT"] => { hrs ฌ 0; dst ฌ FALSE }; Rope.Equal[zoneRope, "BST"] => { hrs ฌ 0; dst ฌ TRUE }; Rope.Equal[zoneRope, "PST"] => { hrs ฌ 8; dst ฌ FALSE }; Rope.Equal[zoneRope, "MST"] => { hrs ฌ 7; dst ฌ FALSE }; Rope.Equal[zoneRope, "CST"] => { hrs ฌ 6; dst ฌ FALSE }; Rope.Equal[zoneRope, "EST"] => { hrs ฌ 5; dst ฌ FALSE }; Rope.Equal[zoneRope, "PDT"] => { hrs ฌ 8; dst ฌ TRUE }; Rope.Equal[zoneRope, "MDT"] => { hrs ฌ 7; dst ฌ TRUE }; Rope.Equal[zoneRope, "CDT"] => { hrs ฌ 6; dst ฌ TRUE }; Rope.Equal[zoneRope, "EDT"] => { hrs ฌ 5; dst ฌ TRUE }; Rope.Equal[zoneRope, "JST"] => { hrs ฌ 9; minus ฌ TRUE; dst ฌ FALSE }; ENDCASE => GOTO bogus; }; 5 => { SELECT text[start] FROM '+ => NULL; '- => minus ฌ TRUE; ENDCASE => GOTO bogus; hrs ฌ ParseDigits[text, start+1, start+3]; mins ฌ ParseDigits[text, start+3, start+5]; }; ENDCASE => GOTO bogus; IF hrs IN[0..24) AND mins IN[0..60) THEN { info.zone ฌ hrs*60+mins; IF minus THEN info.zone ฌ -info.zone; info.dst ฌ dst; } ELSE GOTO bogus; EXITS bogus => RETURN [nullZoneInfo]; }; GetDateAndLineSep: PROC [in: STREAM, text: REF TEXT] RETURNS [date: DFUtilities.Date ฌ []] = { State: TYPE ~ { date1, -- awaiting beginning of date date2, -- saw "~", awaiting "=" date3, -- awaiting end of line after date day, -- in day part of date month1, -- awaiting month part of date month2, -- in month part of date year1, -- awaiting year part of date year2, -- in year part of date hour1, -- awaiting hour part of date hour2, -- in hour part of date minute1, -- awaiting minute part of date minute2, -- in minute part of date second1, -- awaiting second part of date second2, -- in second part of date zone1, -- awaiting zone part of date zone2, -- in alphabetic zone part of date (like "PST") zone3 -- in numeric zone part of date (like "+0200") }; state: State ฌ date1; dayStart, dayStop: NAT ฌ 0; monthStart, monthStop: NAT ฌ 0; yearStart, yearStop: NAT ฌ 0; hourStart, hourStop: NAT ฌ 0; minuteStart, minuteStop: NAT ฌ 0; secondStart, secondStop: NAT ฌ 0; zoneStart, zoneStop: NAT ฌ 0; append: BOOL ฌ FALSE; text.length ฌ 0; DO char: CHAR ~ in.GetChar[ ! IO.EndOfStream => EXIT]; index: NAT ~ text.length; { SELECT state FROM date1 => SELECT char FROM Ascii.SP, Ascii.TAB => { }; Ascii.CR, Ascii.LF => { date.format ฌ omitted; EXIT }; '> => { date.format ฌ greaterThan; state ฌ date3 }; '# => { date.format ฌ notEqual; state ฌ date3 }; '~ => { state ฌ date2 }; IN ['0..'9] => { append ฌ TRUE; dayStart ฌ index; state ฌ day }; ENDCASE => { GOTO bogus }; date2 => SELECT char FROM '= => { date.format ฌ notEqual; state ฌ date3 }; ENDCASE => { GOTO bogus }; date3 => SELECT char FROM Ascii.SP, Ascii.TAB => { }; Ascii.CR, Ascii.LF => { EXIT }; ENDCASE => { ERROR SyntaxError["Unrecognizable text where end-of-line was expected."] }; day => SELECT char FROM IN ['0..'9] => { }; '-, Ascii.SP => { dayStop ฌ index; state ฌ month1 }; ENDCASE => { GOTO bogus }; month1 => SELECT char FROM Ascii.SP => { }; IN ['A..'Z] => { monthStart ฌ index; state ฌ month2 }; ENDCASE => { GOTO bogus }; month2 => SELECT char FROM IN ['a..'z] => { }; '-, Ascii.SP => { monthStop ฌ index; state ฌ year1 }; ENDCASE => { GOTO bogus }; year1 => SELECT char FROM Ascii.SP => { }; IN ['0..'9] => { yearStart ฌ index; state ฌ year2 }; ENDCASE => { GOTO bogus }; year2 => SELECT char FROM IN ['0..'9] => { }; Ascii.SP, Ascii.TAB => { yearStop ฌ index; state ฌ hour1 }; ENDCASE => { GOTO bogus }; hour1 => SELECT char FROM Ascii.SP => { }; IN ['0..'9] => { hourStart ฌ index; state ฌ hour2 }; ENDCASE => { GOTO bogus }; hour2 => SELECT char FROM IN ['0..'9] => { }; ': => { hourStop ฌ index; state ฌ minute1 }; ENDCASE => { GOTO bogus }; minute1 => SELECT char FROM IN ['0..'9] => { minuteStart ฌ index; state ฌ minute2 }; ENDCASE => { GOTO bogus }; minute2 => SELECT char FROM IN ['0..'9] => { }; ': => { minuteStop ฌ index; state ฌ second1 }; ENDCASE => { GOTO bogus }; second1 => SELECT char FROM IN ['0..'9] => { secondStart ฌ index; state ฌ second2 }; ENDCASE => { GOTO bogus }; second2 => SELECT char FROM IN ['0..'9] => { }; Ascii.SP => { secondStop ฌ index; state ฌ zone1 }; ENDCASE => { GOTO bogus }; zone1 => SELECT char FROM Ascii.SP => { }; IN ['A..'Z] => { zoneStart ฌ index; state ฌ zone2 }; '+, '- => { zoneStart ฌ index; state ฌ zone3 }; ENDCASE => { GOTO bogus }; zone2 => SELECT char FROM IN ['A..'Z] => { }; Ascii.CR, Ascii.LF => { zoneStop ฌ index; EXIT }; Ascii.SP, Ascii.TAB => { zoneStop ฌ index; append ฌ FALSE; state ฌ date3 }; ENDCASE => { GOTO bogus }; zone3 => SELECT char FROM IN ['0..'9] => { }; Ascii.CR, Ascii.LF => { zoneStop ฌ index; EXIT }; Ascii.SP, Ascii.TAB => { zoneStop ฌ index; append ฌ FALSE; state ฌ date3 }; ENDCASE => { GOTO bogus }; ENDCASE => ERROR; }; IF append THEN text ฌ RefText.InlineAppendChar[to: text, from: char]; ENDLOOP; IF text.length>0 THEN { u: BasicTime.Unpacked ฌ []; day: INT ~ ParseDigits[text, dayStart, dayStop]; month: BasicTime.MonthOfYear ~ ParseMonth[text, monthStart, monthStop]; year: INT ~ ParseDigits[text, yearStart, yearStop]; hour: INT ~ ParseDigits[text, hourStart, hourStop]; minute: INT ~ ParseDigits[text, minuteStart, minuteStop]; second: INT ~ ParseDigits[text, secondStart, secondStop]; z: ZoneInfo ~ ParseZone[text, zoneStart, zoneStop]; IF year IN[0..100) THEN u.year ฌ (IF year>30 THEN 1900 ELSE 2000)+year ELSE GOTO bogus; IF month#unspecified THEN u.month ฌ month ELSE GOTO bogus; IF day IN[1..BasicTime.daysPerMonth] THEN u.day ฌ day ELSE GOTO bogus; IF hour IN[0..BasicTime.hoursPerDay) THEN u.hour ฌ hour ELSE GOTO bogus; IF minute IN[0..BasicTime.minutesPerHour) THEN u.minute ฌ minute ELSE GOTO bogus; IF second IN[0..BasicTime.secondsPerMinute) THEN u.second ฌ second ELSE GOTO bogus; IF z#nullZoneInfo THEN { u.zone ฌ z.zone; u.dst ฌ IF z.dst THEN yes ELSE no }; date.gmt ฌ BasicTime.Pack[u ! BasicTime.OutOfRange => GOTO bogus]; date.format ฌ explicit; }; EXITS bogus => ERROR SyntaxError["Illegal date specification."]; }; SyntaxError: PUBLIC ERROR [reason: ROPE] = CODE; WriteToStream: PUBLIC PROC [out: STREAM, proc: DFUtilities.SupplyItemProc] = { haveDirectory: BOOL ฌ FALSE; DO item: REF ANY = proc[]; IF item = NIL THEN EXIT; WITH item SELECT FROM directory: REF DFUtilities.DirectoryItem => haveDirectory ฌ TRUE; file: REF DFUtilities.FileItem => IF ~haveDirectory THEN ERROR SyntaxError["File item without preceding Directory item."]; imports: REF DFUtilities.ImportsItem => haveDirectory ฌ FALSE; include: REF DFUtilities.IncludeItem => haveDirectory ฌ FALSE; ENDCASE; WriteItemToStream[out, item]; ENDLOOP; }; WriteItemToStream: PUBLIC PROC [out: STREAM, item: REF ANY] = { maxUsingLineLength: INT = 90; maxReasonableFileNameLength: INT = 45; WITH item SELECT FROM directory: REF DFUtilities.DirectoryItem => { IF directory.exported THEN out.PutRope["Exports "]; IF directory.readOnly THEN out.PutRope["ReadOnly "]; IF ~(directory.exported OR directory.readOnly) THEN out.PutRope["Directory "]; out.PutRope[directory.path1]; }; file: REF DFUtilities.FileItem => { out.PutRope[" "]; IF file.verifyRoot THEN out.PutChar['+]; out.PutRope[file.name]; THROUGH [0..MAX[maxReasonableFileNameLength-file.name.Length[], 1]) DO out.PutChar[Ascii.SP]; ENDLOOP; DateToStream[out, file.date]; }; imports: REF DFUtilities.ImportsItem => { IF imports.exported THEN out.PutRope["Exports "]; out.PutRope["Imports "]; out.PutRope[imports.path1]; out.PutRope[" Of "]; DateToStream[out, imports.date]; SELECT imports.form FROM exports => NULL; all => { out.PutChar[LineSep]; out.PutRope[" Using All"]; }; list => { out.PutChar[LineSep]; out.PutRope[" Using ["]; IF imports.list ~= NIL THEN FOR i: NAT IN [0..imports.list.nEntries) DO entry: DFUtilities.UsingEntry = imports.list.u[i]; IF i # 0 THEN out.PutRope[", "]; IF entry.verifyRoot THEN out.PutChar['+]; out.PutRope[entry.name]; ENDLOOP; out.PutRope["]"]; }; ENDCASE; }; include: REF DFUtilities.IncludeItem => { out.PutRope["Include "]; out.PutRope[include.path1]; out.PutRope[" Of "]; DateToStream[out, include.date]; }; comment: REF DFUtilities.CommentItem => out.PutRope[comment.text]; whiteSpace: REF DFUtilities.WhiteSpaceItem => THROUGH [0..whiteSpace.lines-1) DO out.PutChar[LineSep]; ENDLOOP; ENDCASE => ERROR SyntaxError["Unrecognizable item."]; out.PutChar[LineSep]; }; SortUsingList: PUBLIC PROC [usingList: REF DFUtilities.UsingList, nearlySorted: BOOL] = { IF usingList = NIL THEN RETURN; IF nearlySorted THEN { FOR i: NAT IN [1..usingList.nEntries) DO item: DFUtilities.UsingEntry = usingList.u[i]; j: NAT ฌ i; -- 'j' can't be a loop control variable; we want its value on termination. WHILE Rope.Compare[item.name, usingList.u[j-1].name, FALSE] = less DO usingList.u[j] ฌ usingList.u[j-1]; IF (j ฌ j.PRED) = 0 THEN EXIT; ENDLOOP; IF j ~= i THEN usingList.u[j] ฌ item; -- test avoids unnecessary reference counting. ENDLOOP; } ELSE { SiftUp: PROC [low, high: NAT] = { k: NAT ฌ low; DO twoK: NAT = k*2; son: NAT ฌ twoK; IF twoK > high THEN EXIT; IF twoK+1 <= high AND Rope.Compare[usingList.u[twoK+1-1].name, usingList.u[twoK-1].name, FALSE] ~= less THEN son ฌ twoK+1; IF Rope.Compare[usingList.u[son-1].name, usingList.u[k-1].name, FALSE] = less THEN EXIT; Exchange[son-1, k-1]; k ฌ son; ENDLOOP; }; Exchange: PROC [a, b: NAT] = { temp: DFUtilities.UsingEntry = usingList.u[a]; usingList.u[a] ฌ usingList.u[b]; usingList.u[b] ฌ temp; }; FOR i: NAT DECREASING IN [1..usingList.nEntries/2] DO SiftUp[i, usingList.nEntries]; ENDLOOP; FOR i: NAT DECREASING IN [1..usingList.nEntries) DO Exchange[0, i]; SiftUp[1, i]; ENDLOOP; }; }; SearchUsingList: PUBLIC PROC [file: ROPE, list: REF DFUtilities.UsingList] RETURNS [found: BOOL ฌ FALSE, index: NAT ฌ 0] = { IF list = NIL THEN found ฌ TRUE -- NIL list is interpreted as "everything" ELSE { low: INTEGER ฌ 0; high: INTEGER ฌ list.nEntries.PRED; UNTIL low > high DO probe: NAT ฌ (low + high) / 2; SELECT file.Compare[list.u[probe].name, FALSE] FROM equal => RETURN [TRUE, probe]; less => IF probe = 0 THEN EXIT ELSE high ฌ probe.PRED; greater => IF probe = list.nEntries.PRED THEN EXIT ELSE low ฌ probe.SUCC; ENDCASE; ENDLOOP; found ฌ FALSE; }; }; DifferenceOfUsingLists: PUBLIC PROC [a, b: REF DFUtilities.UsingList] RETURNS [diff: REF DFUtilities.UsingList ฌ NIL] = { aI, bI: NAT ฌ 0; aL: NAT = a.nEntries; bL: NAT = b.nEntries; diff ฌ NEW[DFUtilities.UsingList[a.nEntries]]; diff.nEntries ฌ 0; UNTIL aI = aL OR bI = bL DO SELECT a.u[aI].name.Compare[b.u[bI].name, FALSE] FROM equal => aI ฌ aI.SUCC; less => { diff.u[diff.nEntries] ฌ a.u[aI]; diff.nEntries ฌ diff.nEntries.SUCC; aI ฌ aI.SUCC; }; greater => bI ฌ bI.SUCC; ENDCASE; ENDLOOP; UNTIL aI = aL DO diff.u[diff.nEntries] ฌ a.u[aI]; diff.nEntries ฌ diff.nEntries.SUCC; aI ฌ aI.SUCC; ENDLOOP; }; DateToRope: PUBLIC PROC [date: DFUtilities.Date] RETURNS [ROPE ฌ NIL] = { SELECT date.format FROM $explicit => { months: ROPE = "JanFebMarAprMayJunJulAugSepOctNovDec"; up: BasicTime.Unpacked = BasicTime.Unpack[date.gmt ! BasicTime.OutOfRange, BasicTime.TimeParametersNotKnown => GO TO noDate]; ConvertZone: PROC RETURNS [ROPE] = { dst: BOOL = up.dst = yes; jstZone: BasicTime.Zone = -9*BasicTime.minutesPerHour; SELECT up.zone FROM 0 => RETURN [IF dst THEN "BST" ELSE "GMT"]; NAT[5*BasicTime.minutesPerHour] => RETURN [IF dst THEN "EDT" ELSE "EST"]; NAT[6*BasicTime.minutesPerHour] => RETURN [IF dst THEN "CDT" ELSE "CST"]; NAT[7*BasicTime.minutesPerHour] => RETURN [IF dst THEN "MDT" ELSE "MST"]; NAT[8*BasicTime.minutesPerHour] => RETURN [IF dst THEN "PDT" ELSE "PST"]; jstZone => RETURN["JST"]; ENDCASE; RETURN [ IO.PutFR["%g%02d%02d", [character[IF up.zone < 0 THEN '- ELSE '+]], [cardinal[up.zone.ABS/BasicTime.minutesPerHour]], [cardinal[up.zone.ABS MOD BasicTime.minutesPerHour]] ] ] }; RETURN [ IO.PutFLR["%02d-%g-%02d %02d:%02d:%02d %g", LIST[ [cardinal[up.day]], [rope[months.Substr[start: up.month.ORD*3, len: 3]]], [cardinal[up.year MOD 100]], [cardinal[up.hour]], [cardinal[up.minute]], [cardinal[up.second]], [rope[ConvertZone[]]] ]] ] }; $notEqual => RETURN ["~="]; $greaterThan => RETURN [">"]; ENDCASE; EXITS noDate => {}; }; DateToStream: PUBLIC PROC [s: STREAM, date: DFUtilities.Date] = { s.PutRope[DateToRope[date] ] }; derivedList: LIST OF ROPE ฌ LIST[".bcd", ".boot", ".press", ".signals", ".mob", ".o", ".ip", ".interpress", ".c2c.c", ".dvi", ".$cheme", ".sx.c"]; IsExtension: PROC [base, ext: ROPE] RETURNS [BOOL] ~ { bsize: INT ~ Rope.Length[base]; esize: INT ~ Rope.Length[ext]; i: INT ~ Rope.FindBackward[s1: base, s2: ext, case: FALSE]; IF i<0 THEN RETURN[FALSE]; -- ext not found IF (i+esize) RETURN [pos]; '., '>, '] => RETURN [len]; ENDCASE; ENDLOOP; RETURN [len]; }; RaiseSyntaxError: PROC [prefix: ROPE, text: REF TEXT] = { ERROR SyntaxError[IO.PutFR["%g '%g'.", [rope[prefix]], [text[text]] ] ]; }; END. Ž DFUtilitiesImpl.mesa Copyright ำ 1984, 1985, 1986, 1987, 1988, 1989, 1990, 1991 by Xerox Corporation. All rights reserved. created by Levin Mike Spreitzer January 8, 1987 5:28:21 pm PST Russ Atkinson (RRA) January 19, 1987 1:38:57 pm PST Doug Wyatt, February 26, 1987 9:59:24 pm PST Last tweaked by Mike Spreitzer on July 20, 1988 1:01:20 pm PDT Last edited by: Mik Lamming - January 25, 1989 1:26:28 pm GMT Willie-Sue, January 25, 1989 3:54:59 pm PST Eduardo Pelegri-Llopart March 2, 1989 10:51:34 am PST Tim Diebert: May 25, 1989 1:56:50 pm PDT Michael Plass, December 12, 1991 11:44 am PST Willie-s, June 26, 1991 2:41 pm PDT Exported Procedures bufferT is used for terminals, like "Directory". bufferN is used for non-terminals, like file names, dates, and the like. Separate buffers are used to simplify the REF TEXT handling by clients of GetTokenAsRefText. They are not released if ParseInner exits with an uncaught signal, but this is claimed to be OK. RRA sez: we should consider the imports of any imports item being exported when there is an explicit filter list. Of course, if there is a null intersection (list = NIL), we don't have to consider it unless the form dictates it. enter name in list This case can only happen if filter.list = NIL form _ $exports-- Main parsing loop blank line Comment line Remove this code when old-style comments are no longer supported. convert to new style SELECT text[start+0] FROM 'P => hrs _ 8; 'M => hrs _ 7; 'C => hrs _ 6; 'E => hrs _ 5; ENDCASE => GOTO bogus; SELECT text[start+1] FROM 'S => dst _ FALSE; 'D => dst _ TRUE; ENDCASE => GOTO bogus; SELECT text[start+2] FROM 'T => NULL; ENDCASE => GOTO bogus; When compatibility is no longer needed, replace the following NULL with out.PutRope["\N Using Exports"] intro: ROPE = "\N Using ["; charsOnLine: INT _ intro.Length[].PRED; out.PutRope[intro]; IF imports.list ~= NIL THEN { FOR i: NAT IN [0..imports.list.nEntries) DO The following is an approximate check; the line may get a bit longer than `maxUsingLineLength'. entry: DFUtilities.UsingEntry = imports.list.u[i]; IF charsOnLine + entry.name.Length[] > maxUsingLineLength THEN { indent: ROPE = "\N "; IF i ~= 0 THEN out.PutChar[',]; out.PutRope[indent]; charsOnLine _ indent.Length[].PRED; } ELSE IF i ~= 0 THEN {out.PutRope[", "]; charsOnLine _ charsOnLine + 2}; IF entry.verifyRoot THEN {out.PutChar['+]; charsOnLine _ charsOnLine.SUCC}; out.PutRope[entry.name]; charsOnLine _ charsOnLine + entry.name.Length[]; ENDLOOP; }; out.PutChar[']]; Insertion sort Heap sort Do a binary search for "file" in "list". Last edited by: Mik Lamming - January 25, 1989 1:25:04 pm GMT Was not creating BST for GMT + daylight saving so verify couldn't parse date changes to: ParseZone to emit GMT and BST as appropriate Eduardo Pelegri-Llopart March 2, 1989 10:51:08 am PST Made it indiferent to the presence of 015 or 012 in the input DF. It rewrites the DF using \r. changes to: IsLineSep. ส)์•NewlineDelimiter –(cedarcode) style™codešœ™Kšœ ฯeœ[™fKšœ™K™-K™3K™,K™>K™=K™+K™5K™(K™-K™#—˜šฯk ˜ Kš œžœžœžœžœ˜Kšœ žœŸ˜ฎKšœ žœพ˜ฯKšžœžœtžœ ˜KšœžœZ˜gKšœžœJžœ ˜b——K˜šฯnœžœž˜Kšžœ žœ˜$Kšžœž˜—˜Kšžœžœžœ˜Kšžœžœžœžœ˜K˜Kšัbnzฯzะkzก˜Kšะnz กขกขกœ˜K˜š Ÿ œžœžœžœžœžœ˜3Kšžœ žœžœ žœ˜*K˜—K˜—K˜Kšœ™K˜šŸœžœžœžœI˜qKšŸœžœžœ˜šŸ œžœ˜K•StartOfExpansion+ -- [char: CHAR] RETURNS [IO.CharClass] -- šœžœžœ˜Kšœžœžœ˜Kšœžœ˜Kšœžœžœ˜Kšœฆžœžœ‰žœ™บKšœ žœžœ˜.Kšœ žœžœ˜/šŸ œžœ ฯc)œ˜Hšžœž˜Kšœžœžœžœ ˜&Kšœžœžœžœ ˜&Kšžœžœ ˜—K˜—šŸ œžœ ค)œ˜Jšžœž˜Kšœžœžœžœ ˜&Kšœžœžœžœ ˜*Kšžœžœ ˜—K˜—šŸ œžœ ค)œ˜Gšžœž˜Kšœ žœ ˜Kšžœžœžœ˜'—K˜—š Ÿœžœ žœ žœ žœžœ˜KKšœžœžœ#˜3—š Ÿœžœ žœ žœžœ˜7Kšœžœžœ7˜G—šŸ œžœ žœžœžœžœ žœžœ˜\šžœžœžœ˜,šž˜Kšœžœžœžœ˜/šžœž˜Kšœžœžœžœ˜Kšžœžœ˜—Kšžœ˜—šžœ žœ˜K˜4K˜K˜šž˜Kšœžœžœžœ˜/Kšžœžœžœ˜šžœž˜Kšœ žœ žœ˜+šœ žœž˜K˜Kšœžœžœžœ˜Kšžœ˜—Kšœ žœ žœ˜*šœžœž˜Kšœžœžœžœ˜Kšžœžœ˜—Kšžœžœ˜—Kšœ0˜0Kšžœ˜—K˜Kšžœ ˜K˜—Kšžœ˜K˜—Kšœ'žœžœžœ˜IKšžœ˜Kšžœžœžœ ฯb œ˜<—šŸœžœžœžœ˜šžœžœ˜Kš žœžœฅ œžœžœ˜MK˜—š žœžœžœž œžœž˜7Kšœ˜Kšžœ˜—K˜—š Ÿ œžœžœžœžœžœ˜4Kšžœžœ ˜AKšœ˜—š Ÿ œžœžœžœžœ˜0Kšžœžœ˜1Kšœ˜—šŸœžœ˜šžœ0ž˜6KšžœD˜I—Kšœ˜—šŸœžœ˜šžœžœžœ˜1Kšžœžœ8žœžœ˜TKšœ˜K˜—K˜—KšŸœžœ˜.KšŸœžœžœ˜1šŸœžœ žœ žœ žœžœžœžœžœ˜Kšœ(žœ žœ žœ ˜OKšœ(žœ žœ žœ ˜RšŸœžœžœžœ˜1šžœ˜Kšœžœ$ž˜@Kšœžœ#˜KšžœžœA˜Q—šžœ.ž˜4Kšœ8˜8—K˜K˜—Kšœžœ˜Kšžœžœžœžœ?˜ušžœ˜šžœ.ž˜4šžœ˜!Kšœ ˜ Kšœ ˜ Kšœ!˜!Kšœ˜Kšœ˜Kšœ˜——Kšžœž˜K˜—K˜—šŸ œžœžœžœžœžœžœžœ˜gš Ÿœžœžœžœžœ˜6šžœžœž˜Kšœžœ˜Kšœ˜Kšœ3˜3Kšžœžœžœ˜—Kšžœ+˜1Kšœ˜—Kšœ˜Kšžœžœžœ,˜Išžœžœžœ-ž˜BKšžœ#˜(—Kšœ&˜&Kšžœžœžœ?˜^šžœ˜šžœžœ-ž˜Ešžœ˜Kšœ ˜ Kšœ ˜ Kšœ˜Kšœ˜——Kšžœž˜Kšœ˜—K˜—š Ÿœžœ žœžœžœ˜QKšœžœžœ˜ Kšœžœžœ˜Kšœ˜Kšœ'˜'Kšœžœžœ˜&šŸœžœžœžœ˜(Kšžœžœžœžœ˜2šžœ žœžœžœ˜(Kšžœฃžœ<™ๅKšžœžœžœžœ˜6K˜—šžœž˜Kš œ žœ žœžœžœžœ˜?Kš œ žœ žœžœžœ˜,Kšžœ˜—Kšžœžœžœ˜'K˜—šžœ.ž˜4Kšžœ#˜(—šžœ3ž˜7Kšœžœž˜#Kšžœ/˜4—Kšœ&˜&Kšœ%˜%šžœžœžœ˜-šžœ.ž˜4Kšœ8˜8—K˜Kšœ%˜%K˜—šžœžœžœ˜*Kšœ"˜"šžœžœž˜Kšœžœ˜.Kšœžœคœžœ˜?šœžœ˜!Kšœ žœžœ˜Kšœ ˜ šž˜Kšœžœ˜ Kšœžœ˜ Kšœ"˜"šžœž˜šžœž˜Kšœžœ˜ ˜Kšžœ žœžœžœ˜-Kšžœžœ6˜@—Kšžœ˜——Kšžœ)žœžœ?˜tKšœK˜Kšžœžœ˜Kšœ™šžœžœž˜šœžœ˜Kš œžœžœžœžœžœ˜EKšœžœ ˜*Kšœ˜K˜—šœ ˜ Kšœ+ž™.Kšœ žœžœ+˜SKšœ!˜!šžœžœžœž˜#Kšœ˜Kšžœ˜—Kšœ˜K˜—Kšžœ˜—šœ˜Kšœ˜šœ˜Kšžœžœžœ˜-Kšžœ˜—Kšœ˜—Kšœžœ˜#K˜—Kšœ žœ˜Kšžœ˜—K˜—Kšžœžœ:˜J—K˜K˜—šžœ˜K˜ Kšœ™K˜—Kšœ˜Kšžœžœžœ=˜]Kšžœžœžœ?˜_šžœ˜šžœž˜šžœ˜Kšœ ˜ Kšœ ˜ Kšœ ˜ Kšœ˜K˜ Kšœ ˜ K˜——Kšžœž˜K˜—K˜—šŸœžœžœžœ˜@šœžœžœ˜CKšœ˜Kšœ˜Kšœžœ˜ Kšœ˜Kšœ˜—Kšœžœžœ˜ Kšžœžœžœ#˜Jšžœ3ž˜7Kšœžœž˜#Kšžœ/˜4—Kšœ+˜+Kšœ#˜#šžœžœž˜Kšœžœžœ˜EKšœžœžœ˜CKšžœžœ ˜'—šžœ3ž˜9Kšœ8˜8—K˜Kšœ˜Kšžœžœžœ=˜bKšžœžœžœ?˜dKšžœ˜ K˜—Kšœ™šž˜Kšœžœžœžœ˜Kšœžœžœžœ˜3šžœž˜Kšœžœžœžœค˜Kš œžœžœžœžœ˜@Kš œžœžœžœžœ˜>Kš œžœžœžœžœ˜@Kš œžœžœ%žœžœ˜PKš œžœžœžœžœ˜@Kš œžœžœ'žœžœ˜SKš œžœžœžœžœ˜>Kš œžœžœ'žœžœ˜SKš œžœžœžœžœ˜>Kš œžœžœžœžœ˜@Kš œžœžœžœžœ˜>Kš œžœžœžœžœ˜@Kš œžœžœžœžœ˜>Kš œžœžœžœžœ˜@Kšžœžœ˜—Kšžœ˜—šžœž˜Kšœžœ ˜Kšœžœ ˜Kšœžœ˜Kšœžœ˜Kšœžœ˜Kšœžœ˜Kšœžœ˜Kšœžœ ˜Kšœžœ ˜Kšœžœ ˜Kšœžœ ˜Kšœžœ ˜Kšžœžœ˜—Kšžœ žœ˜$K˜—Lšœ žœžœžœ˜:Lšœ@žœ˜Gš Ÿ œžœžœžœžœžœ$˜^Kšœ žœžœ˜Kšœ žœ˜šžœ ž˜˜Kšœžœ$˜7šžœžœž+˜7Kšœ0žœ˜8Kšœ0žœ˜7Kšœ0žœ˜8Kšœ0žœ˜8Kšœ0žœ˜8Kšœ0žœ˜8Kšœ0žœ˜7Kšœ0žœ˜7Kšœ0žœ˜7Kšœ0žœ˜7Kšœ2žœžœ˜FKšžœžœ˜—šžœžœ<™UKšžœžœ™—šžœžœ žœžœ™>Kšžœžœ™—šžœžœžœ™%Kšžœžœ™—K˜—˜šžœ žœžœžœ˜7Kšžœžœ˜—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šœค/˜6Kšœค.˜4K˜—K˜Kšœžœ˜Kšœžœ˜Kšœžœ˜Kšœžœ˜Kšœžœ˜!Kšœžœ˜!Kšœžœ˜Kšœžœžœ˜K˜šž˜Kšœžœžœžœ˜3Kšœžœ˜K˜šžœž˜šœ žœž˜Kšœžœžœ˜Kšœžœžœžœ˜6Kšœ3˜3Kšœ0˜0Kšœ˜Kšžœžœ"˜@Kšžœžœ ˜—šœ žœž˜Kšœ0˜0Kšžœžœ ˜—šœ žœž˜Kšœžœžœ˜Kšœžœžœžœ˜KšžœžœF˜X—šœžœž˜Kšžœ˜Kšœ žœ(˜4Kšžœžœ ˜—šœ žœž˜Kšœžœ˜Kšžœ4˜6Kšžœžœ ˜—šœ žœž˜Kšžœ˜Kšœ žœ)˜5Kšžœžœ ˜—šœ žœž˜Kšœžœ˜Kšžœ2˜4Kšžœžœ ˜—šœ žœž˜Kšžœ˜Kšœžœžœ(˜;Kšžœžœ ˜—šœ žœž˜Kšœžœ˜Kšžœ2˜4Kšžœžœ ˜—šœ žœž˜Kšžœ˜Kšœ,˜,Kšžœžœ ˜—šœ žœž˜Kšžœ6˜8Kšžœžœ ˜—šœ žœž˜Kšžœ˜Kšœ.˜.Kšžœžœ ˜—šœ žœž˜Kšžœ6˜8Kšžœžœ ˜—šœ žœž˜Kšžœ˜Kšœžœ*˜2Kšžœžœ ˜—šœ žœž˜Kšœžœ˜Kšžœ2˜4Kšœ/˜/Kšžœžœ ˜—šœ žœž˜Kšžœ˜Kšœžœžœžœ˜1Kšœžœžœ!žœ˜KKšžœžœ ˜—šœ žœž˜Kšžœ˜Kšœžœžœžœ˜1Kšœžœžœ!žœ˜KKšžœžœ ˜—Kšžœžœ˜—K˜Kšžœžœ7˜EKšžœ˜—šžœžœ˜K˜Kšœžœ(˜0KšœG˜GKšœžœ*˜3Kšœžœ*˜3Kšœžœ.˜9Kšœžœ.˜9Kšœ3˜3Kšžœžœ žœ žœ žœžœ žœžœ˜WKšžœžœžœžœ˜:Kš žœžœžœ žœžœ˜FKš žœžœžœžœžœ˜HKš žœžœžœžœžœ˜QKš žœžœ žœžœžœ˜SKš žœžœžœžœžœ˜NKšœ6žœ˜BK˜K˜—Kšžœ žœ,˜@K˜—š Ÿ œžœžœ žœžœ˜0K˜—šŸ œžœžœžœ'˜NKšœžœžœ˜šž˜Kšœžœžœ ˜Kšžœžœžœžœ˜šžœžœž˜Kšœ žœ.žœ˜Ašœžœ˜!šžœž˜Kšžœ<˜A——Kšœ žœ,žœ˜>Kšœ žœ,žœ˜>Kšžœ˜—Kšœ˜Kšžœ˜—K˜—K˜š Ÿœžœžœžœžœžœ˜?Kšœžœ˜Kšœžœ˜&šžœžœž˜šœ žœ˜-Kšžœžœ˜3Kšžœžœ˜4Kšžœžœžœ˜NK˜K˜—šœžœ˜#K˜Kšžœžœ˜(K˜šžœžœ5ž˜FKšœžœ˜Kšžœ˜—Kšœ˜K˜—šœ žœ˜)Kšžœžœ˜1Kšœ˜Kšœ˜Kšœ˜Kšœ ˜ šžœž˜šœG™GKšœ ™ —Kšœ žœ˜šœ˜Kšœ ฅœ˜Kšœ˜K˜—˜ Kšœ ฅœ˜Kšœ˜š žœžœžœžœžœžœž˜GKšœ2˜2Kšžœžœ˜ Kšžœžœ˜)Kšœ˜Kšžœ˜—K˜K˜Kšœžœ™Kšœ žœžœ™'Kšœ™šžœžœžœ™šžœžœžœž™+Kšœ_™_Kšœ2™2šžœ8žœ™@Kšœžœ ™Kšžœžœ™Kšœ™Kšœžœ™#K™—šž™Kšžœžœ4™B—Kšžœžœ,žœ™KKšœ™Kšœ0™0Kšžœ™—K™—K™—Kšžœ˜—K˜—šœ žœ˜)Kšœ˜Kšœ˜Kšœ˜Kšœ ˜ K˜—šœ žœ˜'K˜—šœ žœ˜-Kšžœžœ ฅœžœ˜A—Kšžœžœ%˜5—Kšœ ฅœ˜K˜K˜—š Ÿ œžœžœ žœ&žœ˜YKšžœ žœžœžœ˜šžœ ˜šžœ˜Kšœ™šžœžœžœž˜(Kšœ.˜.KšœžœคJ˜Wšžœ0žœ ž˜EKšœ"˜"Kšžœžœžœžœ˜Kšžœ˜—Kšžœžœค.˜UKšžœ˜—K˜—šžœ˜Kšœ ™ šŸœžœ žœ˜!Kšœžœ˜ šž˜Kšœžœ˜Kšœžœ˜Kšžœ žœžœ˜KšžœžœDžœ žœ˜zKšžœ>žœ žœžœ˜XK˜K˜Kšžœ˜—K˜—šŸœžœžœ˜Kšœ.˜.K˜ K˜K˜—š žœžœž œžœž˜5Kšœ˜Kšžœ˜—š žœžœž œžœž˜3Kšœ˜Kšœ ˜ Kšžœ˜—K˜——K˜—K˜šŸœžœžœžœžœžœ žœžœ žœ ˜|Kšœ(™(šžœž˜ Kšžœ žœค*˜=šžœ˜Kšœžœ˜Kšœžœžœ˜#šžœ ž˜Kšœžœ˜šžœ"žœž˜3Kšœ žœžœ ˜Kš œžœ žœžœžœžœ˜6Kš œ žœžœžœžœžœ žœ˜IKšžœ˜—Kšžœ˜—Kšœžœ˜K˜——K˜—K˜šŸœžœžœžœžœžœžœ˜yKšœžœ˜Kšœžœ˜Kšœžœ˜Kšœžœ$˜.Kšœ˜šžœ žœ ž˜šžœ$žœž˜5Kšœžœ˜˜ Kšœ ˜ Kšœžœ˜#Kšœžœ˜ Kšœ˜—Kšœžœ˜Kšžœ˜—Kšžœ˜—šžœ ž˜Kšœ ˜ Kšœžœ˜#Kšœžœ˜ Kšžœ˜—K˜—K˜š Ÿ œžœžœžœžœžœ˜Išžœ ž˜šœ˜Kšœžœ*˜6˜2Kšœ<žœžœ ˜J—šŸ œžœžœžœ˜$Kšœžœ˜Kšœ6˜6šžœ ž˜Kš œžœžœžœžœ˜+Kš žœ žœžœžœžœ˜IKš žœ žœžœžœžœ˜IKš žœ žœžœžœžœ˜IKš žœ žœžœžœžœ˜IKšœ žœ˜Kšžœ˜—šžœ˜šžœ˜Kšœ žœ žœžœ˜,Kšœžœ˜1Kšœžœžœ˜4K˜—K˜—K˜—šžœ˜šžœ*žœ˜1Kšœ˜Kšœ$žœ˜5Kšœžœ˜Kšœ˜Kšœ˜Kšœ˜Kšœ˜K˜—K˜—K˜—Kšœ žœ˜Kšœžœ˜Kšžœ˜—šž˜Kšœ ˜ —K˜—K˜šŸ œžœžœžœ˜?Kšœ!˜!K˜—Kš œ žœžœžœžœr˜’K˜š Ÿ œžœ žœžœžœ˜6Kšœžœ˜Kšœžœ˜Kšœžœ.žœ˜;Kš žœžœžœžœค˜+Kšžœžœžœ ค˜XKšžœžœค˜"K˜K˜—š Ÿœžœžœžœžœ˜Qš žœžœžœžœžœžœž˜CKšžœ*žœžœ ˜CKšžœ˜—Kšžœ ˜K˜K˜—š Ÿœžœžœžœžœžœ˜:Kšžœ(˜.K˜—K˜š Ÿœžœžœžœžœžœ˜=Kšžœ&˜,K˜K˜—š Ÿœžœžœžœžœ˜3Kšœžœ˜Kšœžœ˜šžœž˜šžœž˜Kšœžœ˜Kšœžœ˜Kšžœ˜—Kšžœ˜—Kšžœ˜ K˜K™—š Ÿœžœ žœžœžœ˜9Kšžœ žœ4˜HK˜—K™Kšžœ˜K˜™=K™LKšœ ฯr œ#™8—™5K™_Kšœ ฆ œ™—K™—…—rงˆ