<<>> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> DIRECTORY ARAccess USING [AppendChar], Ascii USING [CR, LF, NUL, SP, TAB], CmFile USING [ErrorCode, Handle, noMatch, TableDesc], <> <> <> IO USING [Close, EndOfStream, GetChar, GetIndex, SetIndex, STREAM], <> <<[dontRelease, Error, GetProperties, Handle, maxNameLength, ReadOnly, Release],>> <> PFS USING [Close, Error, Open, OpenFile, PathFromRope, StreamFromOpenFile], Rope USING [Compare, Concat, Equal, Fetch, IsEmpty, Length, ROPE, Substr], <> <> <> <> <> <> <> <> Token USING [ Filtered, FilterProcType, GetCharProcType, Handle, Line, nonQuote, Object, QuoteProcType--, WhiteSpace--]; <> CmFilesA: CEDAR MONITOR IMPORTS ARAccess, --Event, Heap, --IO, --MFile, MStream, --PFS, Rope--, Stream, String, StringLookUp, --Supervisor, System--, Token--, Volume EXPORTS CmFile = BEGIN <> lineSize: CARDINAL = 250; noSection: LONG CARDINAL = LAST[LONG CARDINAL]; <> sectionHints: HintHandle ¬ NIL; userCm: IO.STREAM ¬ NIL; --MStream.Handle ¬ NIL; readingCount: CARDINAL ¬ 0; resetStream: BOOLEAN ¬ FALSE; <> HintHandle: TYPE = REF HintObject; HintObject: TYPE = RECORD [ name: Rope.ROPE ¬ NIL, position: LONG CARDINAL ¬ 0, next: HintHandle ¬ NIL]; Handle: TYPE = REF Object; Object: TYPE = MACHINE DEPENDENT RECORD [ tokenData: Token.Object, sh: IO.STREAM, --Stream.Handle, specificIndex: LONG CARDINAL ¬ noSection]; AbsToRep: PROCEDURE [h: CmFile.Handle] RETURNS [Handle] = TRUSTED {RETURN[LOOPHOLE[h]]}; RepToAbs: PROCEDURE [h: Handle] RETURNS [CmFile.Handle] = { RETURN[NEW[Token.Object ¬ h.tokenData]]}; Validate: PROCEDURE [h: CmFile.Handle] RETURNS [repH: Handle] = BEGIN repH ¬ AbsToRep[h]; IF repH.tokenData.getChar # GetChar THEN ERROR Error[invalidHandle]; END; <> <> <
> <> <> <> <> <> <> <> <> beginSectionName: CHARACTER = '[; commentChar: CHARACTER = '-; endSectionName: CHARACTER = ']; nameBreak: CHARACTER = ':; separateSectionName: CHARACTER = ':; <> Error: PUBLIC SIGNAL [code: CmFile.ErrorCode] = CODE; TableError: PUBLIC SIGNAL [h: CmFile.Handle, name: Rope.ROPE] = CODE; <> <<>> emptyKey: CARD = CmFile.noMatch - 1; ambiguous: CARD = emptyKey - 1; UpperCase: PROCEDURE[c: CHAR] RETURNS[CHAR] ~{ IF c <= 'z AND c >= 'a THEN RETURN[c - 'a + 'A]; RETURN[c]; }; InitialInternal: PUBLIC PROCEDURE [ key, entry: Rope.ROPE, maxLength: CARDINAL, caseFold: BOOLEAN ¬ TRUE] RETURNS [matchLength: CARDINAL] = < 0>> <= maxLength AND entry.length >= maxLength>> { matchLength ¬ 0; IF caseFold THEN DO IF UpperCase[key.Fetch[matchLength]] # UpperCase[entry.Fetch[matchLength]] THEN EXIT; IF (matchLength ¬ matchLength + 1) = maxLength THEN EXIT; ENDLOOP ELSE DO IF key.Fetch[matchLength] # entry.Fetch[matchLength] THEN EXIT; IF (matchLength ¬ matchLength + 1) = maxLength THEN EXIT; ENDLOOP; }; InTable: PUBLIC PROCEDURE [key: Rope.ROPE, table: CmFile.TableDesc, caseFold: BOOLEAN ¬ TRUE, noAbbreviation: BOOLEAN ¬ FALSE] RETURNS [index: CARDINAL] = <> { keyLength: INT; IF Rope.IsEmpty[key] THEN {index ¬ emptyKey; GOTO Return}; index ¬ CmFile.noMatch; keyLength ¬ key.Length[]; FOR i: CARDINAL IN [0..table.len) DO entry: Rope.ROPE = table[i]; lengthsAreSame: BOOLEAN; SELECT entry.Length[] FROM < keyLength => LOOP; -- empty entry takes this arm -- keyLength => lengthsAreSame ¬ TRUE; ENDCASE => IF noAbbreviation THEN LOOP ELSE lengthsAreSame ¬ FALSE; { leng: INT ¬ InitialInternal[key, entry, keyLength, caseFold]; SELECT leng FROM < keyLength => NULL; keyLength => { IF lengthsAreSame THEN {index ¬ i; EXIT}; index ¬ (IF index = CmFile.noMatch THEN i ELSE ambiguous); }; ENDCASE; }; ENDLOOP; EXITS Return => NULL; }; <> FreeString: PUBLIC PROCEDURE [s: Rope.ROPE] RETURNS [nil: Rope.ROPE ¬ NIL] = {--Heap.systemZone.FREE[@s]--}; Close: PUBLIC PROCEDURE [h: CmFile.Handle] RETURNS [nil: CmFile.Handle] = { repH: Handle ¬ Validate[h]; repH.sh.Close --delete--[--repH.sh--]; repH.tokenData.getChar ¬ NIL; -- make sure deallocated object will not look valid <> RETURN[h]}; <> <> <> <> <> <> <> < GOTO NoFile];>> <> <> <> <<[tag, value] _ NextItem[h ! UNWIND => h _ Close[h]];>> <> <> <> <> <> <> <> < NULL;>> <> Open: PUBLIC PROCEDURE [fileName: Rope.ROPE] RETURNS [h: CmFile.Handle] = BEGIN repH: Handle; BEGIN fh: PFS.OpenFile; --MFile.Handle; sh: IO.STREAM; --Stream.Handle; <> <> fh ¬ PFS.Open[ PFS.PathFromRope[fileName], read ! PFS.Error => GOTO Lose]; sh ¬ PFS.StreamFromOpenFile[fh, read ! PFS.Error => {PFS.Close[fh]; GOTO Lose}]; repH ¬ NEW[ Object ¬ [tokenData: [getChar: GetChar, break: Ascii.NUL ], sh: sh]]; EXITS Lose => {SIGNAL Error[fileNotFound]; RETURN[NIL]}; END; h ¬ RepToAbs[repH]; END; ReadLineOrToken: PUBLIC PROCEDURE [ h: Token.Handle, buffer: Rope.ROPE, terminator: CHARACTER] = TRUSTED <> BEGIN buffer ¬ NIL; --buffer.length ¬ 0; <> WHILE SELECT (h.break ¬ h.getChar[h]) FROM Ascii.SP, Ascii.TAB => TRUE, ENDCASE => FALSE DO ENDLOOP; <> DO SELECT h.break FROM terminator, Ascii.CR, Ascii.NUL => EXIT; ENDCASE; buffer ¬ ARAccess.AppendChar[buffer, h.break]; <> < GOTO TooMany];>> h.break ¬ h.getChar[h]; ENDLOOP; < NULL;>> END; TitleMatch: PUBLIC PROCEDURE [buffer, title: Rope.ROPE] RETURNS [BOOLEAN] = BEGIN llSubString, titleSubString: Rope.ROPE; --String.SubStringDescriptor; IF buffer.Length < title.Length + 2 OR buffer.Fetch[0] # beginSectionName OR buffer.Fetch[title.Length + 1] # endSectionName THEN RETURN[FALSE]; llSubString ¬ Rope.Substr[buffer, 1, title.Length]; titleSubString ¬ Rope.Substr[title, 0, title.Length]; RETURN[Rope.Equal[llSubString, titleSubString]]; END; <> WhiteSpace: PUBLIC Token.FilterProcType = { RETURN[WhiteSpaceInline[c]]; }; WhiteSpaceInline: PROCEDURE [c: CHAR] RETURNS [ --isWhiteSpace:-- BOOLEAN] = INLINE { RETURN[SELECT c FROM Ascii.SP, Ascii.TAB, Ascii.CR, Ascii.LF => TRUE, ENDCASE => FALSE]; }; FindSection: PUBLIC PROCEDURE [h: CmFile.Handle, title: Rope.ROPE] RETURNS [opened: BOOLEAN] = BEGIN localLine: Rope.ROPE ¬ NIL; --[lineSize]; repH: Handle = Validate[h]; genericIndex: LONG CARDINAL ¬ noSection; qualifiedTitle: Rope.ROPE; <> localLine ¬ ARAccess.AppendChar[localLine, separateSectionName]; <> localLine ¬ localLine.Concat[title]; <<String.AppendString[localLine, title];>> qualifiedTitle ¬ localLine; <> <> IF IsUserDotCm[repH] THEN { str: Rope.ROPE; doneOne: BOOLEAN ¬ FALSE; temp: HintHandle; SELECT Rope.Compare[qualifiedTitle, title] FROM less => str ¬ qualifiedTitle; greater => str ¬ title; ENDCASE; <> FOR l: HintHandle ¬ sectionHints, temp UNTIL l = NIL DO SELECT Rope.Compare[str, l.name] FROM equal => { IF str = title THEN genericIndex ¬ l.position ELSE repH.specificIndex ¬ l.position; IF doneOne THEN EXIT; temp ¬ l.next}; greater => {temp ¬ l.next; LOOP}; ENDCASE => temp ¬ l; -- didn't find first title, so advance to second title IF doneOne THEN EXIT; doneOne ¬ TRUE; IF str = qualifiedTitle THEN str ¬ title ELSE str ¬ qualifiedTitle; ENDLOOP; <> } ELSE { IO.SetIndex[repH.sh, 0]; DO ENABLE UNWIND => NULL; --Heap.systemZone.FREE[@qualifiedTitle]; ReadLine[h, localLine]; IF h.break = Ascii.NUL THEN EXIT; IF localLine.Length # 0 AND localLine.Fetch[0] = beginSectionName THEN BEGIN IF TitleMatch[localLine, qualifiedTitle] THEN BEGIN repH.specificIndex ¬ IO.GetIndex[repH.sh]; IF genericIndex # noSection THEN EXIT; END; IF TitleMatch[localLine, title] THEN BEGIN genericIndex ¬ IO.GetIndex[repH.sh]; IF repH.specificIndex # noSection THEN EXIT; END; END; ENDLOOP}; <> <> IF opened ¬ (genericIndex # noSection) THEN IO.SetIndex[repH.sh, genericIndex] ELSE opened ¬ ~TrySpecific[h]; END; TrySpecific: PROCEDURE [h: CmFile.Handle] RETURNS [alreadyDone: BOOLEAN] = BEGIN repH: Handle = Validate[h]; IF ~(alreadyDone ¬ (repH.specificIndex = noSection)) THEN { IO.SetIndex[repH.sh, repH.specificIndex]; repH.specificIndex ¬ noSection}; END; NextItem: PUBLIC PROCEDURE [h: CmFile.Handle] RETURNS [name, value: Rope.ROPE] = TRUSTED BEGIN localLine: Rope.ROPE ¬ NIL; --[lineSize]; DO ReadName[h, localLine]; IF h.break = nameBreak THEN EXIT; IF TrySpecific[h] THEN RETURN[NIL, NIL]; ENDLOOP; name ¬ localLine; <> value ¬ Token.Filtered[h: h, data: NIL, filter: Token.Line, skip: whiteSpace, temporary: FALSE]; FOR i: CARDINAL DECREASING IN [0..value.Length) DO IF ~WhiteSpace[value.Fetch[i], NIL] THEN { value ¬ Rope.Substr[value, 0, i+1]; <> EXIT }; ENDLOOP; END; FindItem: PUBLIC PROCEDURE [h: CmFile.Handle, title, name: Rope.ROPE] RETURNS [found: BOOLEAN] = BEGIN localLine: Rope.ROPE ¬ NIL; --[lineSize]; IF ~FindSection[h, title] THEN RETURN[FALSE]; DO GobbleRestOfLine[h]; -- consume any parts of preceding line left over by client ReadName[h, localLine]; IF h.break = nameBreak THEN SELECT TRUE FROM Rope.Equal[name, localLine] => RETURN[TRUE]; ENDCASE => LOOP; IF TrySpecific[h] THEN RETURN[FALSE]; ENDLOOP; END; NextValue: PUBLIC PROCEDURE [h: CmFile.Handle, table: CmFile.TableDesc] RETURNS [index: CARDINAL] = BEGIN localLine: Rope.ROPE ¬ NIL; --[lineSize]; DO GobbleRestOfLine[h]; -- consume any parts of preceding line left over by client ReadName[h, localLine]; IF h.break # nameBreak THEN IF TrySpecific[h] THEN RETURN[CmFile.noMatch] ELSE {h.break ¬ Ascii.CR; LOOP}; index ¬ InTable[localLine, table, TRUE, TRUE].index; IF index < table.len THEN EXIT; SIGNAL TableError[h, localLine]; ENDLOOP; END; <> <> <> <> <> <> <> GetChar: PRIVATE Token.GetCharProcType = BEGIN repH: Handle = AbsToRep[h]; c ¬ IO.GetChar[repH.sh ! IO.EndOfStream => {c ¬ Ascii.NUL; CONTINUE}] END; GobbleRestOfLine: PRIVATE PROCEDURE [h: CmFile.Handle] = TRUSTED BEGIN WHILE SELECT h.break FROM Ascii.CR, Ascii.NUL => FALSE, ENDCASE => TRUE DO h.break ¬ h.getChar[h]; ENDLOOP; END; IsCommentLine: PRIVATE PROCEDURE [s: Rope.ROPE] RETURNS [BOOLEAN] = BEGIN RETURN[s.Length > 1 AND s.Fetch[0] = commentChar AND s.Fetch[1] = commentChar]; END; IsUserDotCm: PRIVATE PROCEDURE [repH: Handle] RETURNS [BOOLEAN] = BEGIN RETURN [FALSE]; <> <<[] _ MFile.GetProperties[MStream.GetFile[repH.sh], name];>> <> END; ReadLine: PRIVATE PROCEDURE [h: CmFile.Handle, buffer: Rope.ROPE] = <> BEGIN DO ReadLineOrToken[h, buffer, Ascii.CR]; IF h.break # Ascii.CR OR ~IsCommentLine[buffer] THEN EXIT; ENDLOOP; END; ReadName: PRIVATE PROCEDURE [h: CmFile.Handle, buffer: Rope.ROPE] = <> <> <> <> BEGIN DO ReadLineOrToken[h, buffer, nameBreak]; IF h.break = Ascii.NUL THEN EXIT; IF buffer.Length # 0 AND ~IsCommentLine[buffer] THEN IF buffer.Fetch[0] = beginSectionName THEN { GobbleRestOfLine[h]; h.break ¬ Ascii.NUL; EXIT} ELSE IF h.break = nameBreak THEN EXIT; GobbleRestOfLine[h]; ENDLOOP; END; CmFileQuote: Token.QuoteProcType = BEGIN RETURN[SELECT c FROM '" => c, ENDCASE => Token.nonQuote]; END; <> <> <> <> <> <> <> <> <> < {c _ Ascii.NUL; CONTINUE}]};>> <<>> <> < CONTINUE];>> <> <> <> <> <> <> <> <> < LOOP;>> <> <> <> <> <> <> <<-1 => list _ z.NEW[HintObject _ [name, position, list]];>> <<0 => z.FREE[@name];>> < {>> <> <