<<>> <> <> <> <> <> <> <> <> <> <> DIRECTORY PFS USING [Error, PathFromRope, StreamOpen], Profiles, ProfilesBackdoor, ProfilesPrivate USING [ProfileChangedItem, ProfileRep], Convert USING [IntFromRope], IO, Rope USING [ROPE, Cat, Concat, Equal, Index, Match, Substr], RuntimeError USING [UNCAUGHT], SymTab USING [Create, EachPairAction, Fetch, Pairs, Ref, Store]; <> ProfilesImpl: CEDAR MONITOR IMPORTS Convert, IO, PFS, Rope, RuntimeError, SymTab--, UXIO-- EXPORTS Profiles, ProfilesBackdoor = BEGIN OPEN Profiles, ProfilesBackdoor; <<>> <<>> <> profileDir: ROPE ¬ NIL; freshProfiles: LIST OF Profile ¬ NIL; <> Profile: PUBLIC TYPE ~ REF ProfileRep; ProfileRep: PUBLIC TYPE ~ ProfilesPrivate.ProfileRep; ProfileChangedItem: TYPE ~ ProfilesPrivate.ProfileChangedItem; Create: PUBLIC PROC [files: LIST OF ROPE, keepFresh: BOOL ¬ TRUE] RETURNS [profile: Profile] ~ { <> SlicesFromFileNames: PROC [files: LIST OF ROPE] RETURNS [slices: LIST OF Slice] ~ { dummy: LIST OF Slice ~ LIST[[rope[NIL]]]; tail: LIST OF Slice ¬ dummy; FOR each: LIST OF ROPE ¬ files, each.rest UNTIL each=NIL DO tail ¬ (tail.rest ¬ LIST[[file[fileName: each.first]]]); ENDLOOP; RETURN [dummy.rest]; }; profile ¬ NEW[ProfileRep ¬ [ entries: NIL, --Set by ParseProfile filesUsed: SymTab.Create[case: FALSE], slices: SlicesFromFileNames[files], keepFresh: keepFresh ]]; ParseProfile[profile]; IF keepFresh THEN freshProfiles ¬ CONS[profile, freshProfiles]; }; <<>> CreateFromRope: PUBLIC PROC [slices: LIST OF ROPE] RETURNS [profile: Profile] ~ { <> SlicesFromRopes: PROC [slices: LIST OF ROPE] RETURNS [LIST OF Slice] ~ { dummy: LIST OF Slice ~ LIST[[rope[NIL]]]; tail: LIST OF Slice ¬ dummy; FOR each: LIST OF ROPE ¬ slices, each.rest UNTIL each=NIL DO tail ¬ (tail.rest ¬ LIST[[rope[each.first]]]); ENDLOOP; RETURN [dummy.rest]; }; profile ¬ NEW[ProfileRep ¬ [ entries: NIL, --Set by ParseProfile filesUsed: SymTab.Create[case: FALSE], slices: SlicesFromRopes[slices], keepFresh: FALSE ]]; ParseProfile[profile]; <> }; <<>> CreateFromSlices: PUBLIC PROC [slices: LIST OF Slice, keepFresh: BOOL ¬ TRUE] RETURNS [profile: Profile] ~ { <> profile ¬ NEW[ProfileRep ¬ [ entries: NIL, --Set by ParseProfile filesUsed: SymTab.Create[case: FALSE], slices: slices, keepFresh: keepFresh ]]; ParseProfile[profile]; IF keepFresh THEN freshProfiles ¬ CONS[profile, freshProfiles]; }; <<>> LetProfileGetStale: PUBLIC ENTRY PROC [profile: Profile] ~ { IF profile.keepFresh THEN { dummy: LIST OF Profile ¬ CONS[NIL, freshProfiles]; FOR each: LIST OF Profile ¬ dummy, each.rest DO --Addr fault => ERROR IF each.rest.first=profile THEN { each.rest ¬ each.rest.rest; EXIT; }; ENDLOOP; profile.keepFresh ¬ FALSE; }; }; <<>> <> <<>> Boolean: PUBLIC PROC [profile: Profile, key: ROPE, default: BOOL ¬ FALSE] RETURNS [value: BOOL] = { entry: ProfileEntry; val: ROPE; entry ¬ Lookup[profile, key]; val ¬ GetRope[entry]; IF val = NIL THEN RETURN [default]; IF val.Equal["TRUE", FALSE] THEN RETURN [TRUE]; IF val.Equal["FALSE", FALSE] THEN RETURN [FALSE]; Report[entry, val.Concat[" is not a Boolean"]]; RETURN [default]; }; Number: PUBLIC PROC [profile: Profile, key: ROPE, default: INT] RETURNS [value: INT] = { entry: ProfileEntry ¬ Lookup[profile, key]; val: ROPE ¬ GetRope[entry]; IF val = NIL THEN RETURN [default]; value ¬ Convert.IntFromRope[val ! RuntimeError.UNCAUGHT => { value ¬ default; Report[entry, val.Concat[" is not an INT"]]; CONTINUE; } ]; }; Token: PUBLIC PROC [profile: Profile, key: ROPE, default: ROPE] RETURNS [value: ROPE] = { entry: ProfileEntry ¬ Lookup[profile, key]; val: ROPE ¬ GetRope[entry]; IF val = NIL THEN RETURN [default]; value ¬ val; }; ListOfTokens: PUBLIC PROC [profile: Profile, key: ROPE, default: LIST OF ROPE] RETURNS [value: LIST OF ROPE] = { entry: ProfileEntry ¬ Lookup[profile, key]; IF entry = NIL THEN RETURN [default] ELSE RETURN [entry.tokens]; }; Line: PUBLIC PROC [profile: Profile, key: ROPE, default: ROPE] RETURNS [value: ROPE] = { entry: ProfileEntry ¬ Lookup[profile, key]; IF entry = NIL THEN RETURN [default]; FOR l: LIST OF ROPE ¬ entry.tokens, l.rest UNTIL l = NIL DO IF value = NIL THEN value ¬ l.first ELSE value ¬ value.Cat[" ", l.first]; ENDLOOP; }; EnumerateKeys: PUBLIC PROC [profile: Profile, pattern: ROPE, proc: EnumProc] ~ { Enum: SymTab.EachPairAction = { <<[key: ROPE, val: SymTab.Val] RETURNS [quit: BOOL _ FALSE]>> IF Rope.Match[pattern: pattern, object: key, case: FALSE] THEN RETURN [proc[key]]; }; [] ¬ SymTab.Pairs[x: profile.entries, action: Enum]; }; HasEntry: PUBLIC ENTRY PROC [profile: Profile, key: ROPE] RETURNS [BOOL] ~ { ENABLE UNWIND => NULL; RETURN [FetchInternal[profile, key].found]; }; Lookup: PUBLIC ENTRY PROC [profile: Profile, key: ROPE] RETURNS [ProfileEntry] = { ENABLE UNWIND => NULL; RETURN [LookupInternal[profile, key]] }; LookupInternal: INTERNAL PROC [profile: Profile, key: ROPE] RETURNS [ProfileEntry] = { RETURN [NARROW[FetchInternal[profile, key].val]]; }; FetchInternal: INTERNAL PROC [profile: Profile, key: ROPE] RETURNS [found: BOOL, val: REF ANY] ~ { [found: found, val: val] ¬ SymTab.Fetch[x: profile.entries, key: key]; }; GetRope: PROC [entry: ProfileEntry] RETURNS [value: ROPE] = INLINE { IF entry = NIL OR entry.tokens = NIL THEN RETURN [NIL]; value ¬ entry.tokens.first; IF entry.tokens.rest # NIL THEN Report[entry, "extra material on line"]; }; GetProfileSlices: PUBLIC PROC [profile: Profile] RETURNS [LIST OF Slice] = { RETURN [profile.slices]; }; GetProfileNames: PUBLIC PROC [profile: Profile] RETURNS [LIST OF ROPE] = { dummy: LIST OF ROPE ~ LIST[NIL]; tail: LIST OF ROPE ¬ dummy; FOR each: LIST OF Slice ¬ profile.slices, each.rest UNTIL each=NIL DO WITH each.first SELECT FROM fileSlice: Slice.file => tail ¬ (tail.rest ¬ LIST[fileSlice.fileName]); ENDCASE; ENDLOOP; RETURN [dummy.rest]; }; <<>> <> NoVersion: PROC [file: ROPE] RETURNS [ROPE] ~ { RETURN [Rope.Substr[base: file, len: Rope.Index[s1: file, s2: "!"]]]; }; ParseProfile: ENTRY PROC [profile: Profile] ~ { ENABLE UNWIND => NULL; TryProfile: INTERNAL PROC [slice: Slice] ~ { stream: IO.STREAM ¬ NIL; WITH slice SELECT FROM slice: Slice.rope => stream ¬ IO.RIS[rope: slice.text]; slice: Slice.file => { profileName: ROPE ¬ Rope.Concat[profileDir, slice.fileName]; < CONTINUE];>> stream ¬ PFS.StreamOpen[fileName: PFS.PathFromRope[profileName], accessOptions: read ! PFS.Error => CONTINUE]; IF stream = NIL THEN RETURN; [] ¬ SymTab.Store[x: profile.filesUsed, key: profileName, val: $Parent]; }; ENDCASE => ERROR; ParseProfileInternal[profile, stream]; }; profile.entries ¬ SymTab.Create[case: FALSE]; profile.filesUsed ¬ SymTab.Create[case: FALSE]; FOR each: LIST OF Slice ¬ profile.slices, each.rest UNTIL each=NIL DO TryProfile[each.first]; ENDLOOP; }; ParseProfileInternal: INTERNAL PROC [profile: Profile, stream: IO.STREAM] ~ { <> DO ENABLE { RuntimeError.UNCAUGHT => EXIT; UNWIND => IO.Close[stream]; }; SkipWhite: PROC [flushLines: BOOL ¬ FALSE] RETURNS [c: CHAR] = { DO ENABLE IO.Error, IO.EndOfStream => GO TO stop; c ¬ stream.PeekChar[]; SELECT c FROM '\n, '\r, '\l => IF NOT flushLines THEN RETURN; <= 40C => {}; '- => {-- could be a comment [] ¬ stream.GetChar[]; IF stream.PeekChar[] # '- THEN { <> stream.Backup[c]; RETURN [c]; }; DO <> c ¬ stream.GetChar[]; SELECT c FROM '\n, '\r, '\l => { <> stream.Backup[c]; IF flushLines THEN EXIT; RETURN; }; '- => IF stream.PeekChar[] = '- THEN EXIT; ENDCASE; ENDLOOP; }; ENDCASE => RETURN; [] ¬ stream.GetChar[]; ENDLOOP; EXITS stop => {c ¬ 0C}; }; LocalToken: PROC [flushLines: BOOL ¬ FALSE] = { stop: CHAR ¬ SkipWhite[flushLines]; position ¬ stream.GetIndex[]; token ¬ NIL; SELECT stop FROM 0C, '\n, '\r, '\l => RETURN; '" => token ¬ stream.GetRopeLiteral[]; ENDCASE => token ¬ stream.GetTokenRope[tokenProc].token; }; tokenProc: IO.BreakProc = { RETURN [SELECT char FROM IO.SP, IO.TAB--, ',-- => sepr, IO.LF, IO.CR, ': => break, ENDCASE => other ]; }; Cat: PROC [a, b: LIST OF ROPE] RETURNS [LIST OF ROPE] ~ { RETURN [IF a=NIL THEN b ELSE CONS[a.first, Cat[a.rest, b]]]; }; LookupTokens: INTERNAL PROC [key: ROPE] RETURNS [tokens: LIST OF ROPE] ~ { entry: ProfileEntry ~ LookupInternal[profile, key]; RETURN [IF entry=NIL THEN NIL ELSE entry.tokens]; }; token: ROPE ¬ NIL; tokens, tail: LIST OF ROPE ¬ NIL; position: INT ¬ 0; key: ROPE ¬ NIL; additive: {none, prefix, suffix}; LocalToken[TRUE]; IF (key ¬ token) = NIL THEN EXIT; SELECT SkipWhite[] FROM ': => { [] ¬ stream.GetChar[]; -- flush the ': IF (additive ¬ SELECT stream.PeekChar[] FROM '< => prefix, '> => suffix, ENDCASE => none)#none THEN [] ¬ stream.GetChar[]; --flush '< or '> if needed }; ENDCASE => { <> DO SELECT stream.GetChar[ ! IO.EndOfStream => EXIT] FROM '\n, '\r, '\l => EXIT; ENDCASE; ENDLOOP; ReportInternal[msg: IO.PutFR1["missing : at [%d]", IO.int[position]]]; LOOP; }; DO list: LIST OF ROPE ¬ NIL; LocalToken[]; IF token = NIL THEN EXIT; list ¬ LIST[token]; IF tail = NIL THEN {tail ¬ tokens ¬ list} ELSE {tail.rest ¬ list; tail ¬ list}; ENDLOOP; IF key.Equal["Include", FALSE] THEN { --Insert a profile slice FOR each: LIST OF ROPE ¬ tokens, each.rest UNTIL each=NIL DO profileSlice: IO.STREAM ¬ NIL; profileName: ROPE ¬ Rope.Concat[profileDir, each.first]; < CONTINUE];>> profileSlice ¬ PFS.StreamOpen[PFS.PathFromRope[profileName], read ! PFS.Error => CONTINUE]; IF profileSlice # NIL THEN { [] ¬ SymTab.Store[x: profile.filesUsed, key: profileName, val: $Child]; ParseProfileInternal[profile, profileSlice]; } ELSE ReportInternal[ entry: NEW[ProfileRecord ¬ [each.first, tokens, position]], msg: "File not found." ]; ENDLOOP; } ELSE { --Add a profile entry SELECT additive FROM prefix => tokens ¬ Cat[tokens, LookupTokens[key]]; suffix => tokens ¬ Cat[LookupTokens[key], tokens]; ENDCASE => IF LookupInternal[profile, key] # NIL THEN ReportInternal[ entry: LookupInternal[profile, key], msg: IO.PutFR["%g also appears at [%d]", [rope[key]], [integer[position]]] ]; [] ¬ SymTab.Store[x: profile.entries, key: key, val: NEW[ProfileRecord ¬ [key, tokens, position]]] }; ENDLOOP; IO.Close[stream]; }; <<>> <> Error: PUBLIC ERROR [reason: ROPE] ~ CODE; Report: PUBLIC ENTRY PROC [entry: ProfileEntry ¬ NIL, msg: ROPE] = { ENABLE UNWIND => NULL; ReportInternal[entry, msg]; }; ReportInternal: INTERNAL PROC [entry: ProfileEntry ¬ NIL, msg: ROPE] = { ENABLE RuntimeError.UNCAUGHT => CONTINUE; msg ¬ IO.PutFR1["\n\n%g", [rope[msg]]]; IF entry#NIL THEN msg ¬ IO.PutFLR["%g, at %g [%d]", LIST[[rope[msg]], [rope[entry.key]], [integer[entry.position]]]]; Error[msg]; }; <<>> <> <> ProfileChangeList: TYPE ~ LIST OF ProfileChangedItem; AddToList: ENTRY PROC [profile: Profile, item: ProfileChangedItem] ~ { dummy: ProfileChangeList ¬ CONS[[NIL, NIL], profile.changeWatchers]; FOR each: ProfileChangeList ¬ dummy, each.rest DO IF each.rest=NIL THEN {each.rest¬LIST[item]; EXIT}; --Append to end ENDLOOP; profile.changeWatchers ¬ dummy.rest; }; CopyList: ENTRY PROC [toCopy: LIST OF ProfileChangedItem] RETURNS [LIST OF ProfileChangedItem] ~ { RETURN [IF toCopy=NIL THEN NIL ELSE CONS[toCopy.first, CopyList[toCopy.rest]]]; }; CallWhenProfileChanges: PUBLIC PROC [profile: Profile, proc: Profiles.ProfileChangedProc, clientData: REF] = { item: ProfileChangedItem ~ [proc, clientData]; IF profile.keepFresh THEN AddToList[profile, item]; DoIt[profile, item, firstTime]; }; ProfileChanged: PUBLIC ProfileChangedProc = { ParseProfile[profile: profile ! RuntimeError.UNCAUGHT => CONTINUE]; FOR each: ProfileChangeList ¬ CopyList[profile.changeWatchers], each.rest UNTIL each=NIL DO DoIt[profile, each.first, reason]; ENDLOOP; }; DoIt: PROC [profile: Profile, item: ProfileChangedItem, reason: Profiles.ProfileChangeReason] = { item.proc[ profile: profile, reason: reason, clientData: item.clientData ! RuntimeError.UNCAUGHT => { Report[msg: "Problem while executing ProfileChangedProc"]; CONTINUE } ]; }; END. <<>>