-- File: CmFilesA.mesa - last edit: -- Smokey, Oct 17, 1979 1:02 PM -- Evans, Aug 19, 1980 10:44 AM -- Tom, Aug 28, 1979 6:01 PM -- Mark, September 5, 1980 1:15 AM -- Karlton, September 5, 1980 1:13 AM -- Levin, 8-Mar-82 17:12:37 DIRECTORY Ascii USING [ControlZ, CR, NUL, SP, TAB], CmFile USING [ErrorCode, Lop], Compatibility USING [SHandle], Segments, Streams, String USING [ AppendChar, EquivalentString, EquivalentSubString, StringBoundsFault, StringToDecimal, SubString, SubStringDescriptor], Storage USING [CopyString, FreeStringNil, String]; CmFilesA: PROGRAM IMPORTS CmFile, Segments, Streams, String, Storage EXPORTS CmFile = BEGIN -- Global Data userDotCmName: STRING = "User.cm"; lineSize: CARDINAL = 250; CmFileInfo: TYPE = RECORD [ keepOpen: BOOLEAN ← FALSE, fh: Segments.FHandle ← NIL, sh: Streams.Handle ← NIL, fileName: STRING ← NIL]; CmFileInfoHandle: TYPE = POINTER TO CmFileInfo; userDotCmInfo: CmFileInfo ← [fileName: userDotCmName]; otherCmInfo: CmFileInfo ← []; lastOpenedCFI: CmFileInfoHandle ← NIL; -- Current file open info quote: CHARACTER = '"; -- SIGNALs Error: PUBLIC SIGNAL [code: CmFile.ErrorCode] = CODE; -- Client collusion -- UserDotCmSetFHHint: PUBLIC PROCEDURE [file: Segments.FHandle] = { -- Segments.LockFile[file]}; -- Simple Interface Procedures Open: PUBLIC PROCEDURE [fileName: STRING] = BEGIN FileOpen[fileName]; lastOpenedCFI.keepOpen ← TRUE; END; Close: PUBLIC PROCEDURE [fileName: STRING] = BEGIN FileClose[fileName]; lastOpenedCFI.keepOpen ← FALSE; END; Line: PUBLIC PROCEDURE [fileName, title, name: STRING] RETURNS [s: STRING] = BEGIN localLine: STRING ← [lineSize]; card: CARDINAL; IF ~OpenAndMatch[localLine, fileName, title, name] THEN s ← NIL ELSE BEGIN s ← Storage.String[localLine.length-name.length]; card ← name.length+2; WHILE card<localLine.length AND localLine[card]=Ascii.SP DO card ← card + 1; ENDLOOP; IF localLine[card]=quote THEN BEGIN FOR card IN (card..localLine.length) DO IF localLine[card]=quote THEN EXIT; String.AppendChar[s, localLine[card]]; ENDLOOP; END ELSE BEGIN FOR card IN [card..localLine.length) DO String.AppendChar[s, localLine[card]]; ENDLOOP; END; END; IF ~lastOpenedCFI.keepOpen THEN Close[fileName]; END; GetNextToken: PUBLIC PROCEDURE [source: String.SubString, token: STRING] RETURNS [valid: BOOLEAN] = BEGIN valid ← RealGetNextToken[source, token, FALSE]; END; GetNextTokenAsANumber: PUBLIC PROCEDURE [source: String.SubString] RETURNS [i: INTEGER] = BEGIN token: STRING ← [20]; IF RealGetNextToken[source, token, TRUE] THEN RETURN[String.StringToDecimal[token]] ELSE RETURN[0]; END; ReadLineOrToken: PUBLIC PROCEDURE [ sh: Compatibility.SHandle, buffer: STRING, terminator: CHARACTER] RETURNS[c: CHARACTER] = BEGIN started: BOOLEAN ← FALSE; buffer.length ← 0; c ← Ascii.NUL; DO SELECT (c ← Streams.GetByte[sh ! Streams.End[] => EXIT]) FROM terminator, Ascii.ControlZ, Ascii.CR => EXIT; Ascii.SP, Ascii.TAB => NULL; ENDCASE => started ← TRUE; IF started THEN String.AppendChar[buffer, c ! String.StringBoundsFault => GOTO TooMany]; ENDLOOP; IF c=Ascii.ControlZ THEN WHILE c # Ascii.CR DO c ← Streams.GetByte[sh ! Streams.End[] => EXIT] ENDLOOP; EXITS TooMany => NULL; END; TitleMatch: PUBLIC PROCEDURE [buffer, title: STRING] RETURNS [BOOLEAN] = BEGIN llSubString, titleSubString: String.SubStringDescriptor; IF buffer.length < title.length+2 THEN RETURN[FALSE]; IF buffer[0] # '[ THEN RETURN[FALSE]; IF buffer[title.length+1] # '] THEN RETURN[FALSE]; llSubString ← [buffer, 1, title.length]; titleSubString ← [title, 0, title.length]; RETURN[String.EquivalentSubString[@llSubString, @titleSubString]] END; -- More efficient ones OpenSection: PUBLIC PROCEDURE [fileName, title: STRING] RETURNS [BOOLEAN] = BEGIN Open[fileName ! Segments.FileNameProblem[] => GOTO badFile]; IF FindSection[title] THEN RETURN[TRUE]; RETURN[FALSE]; EXITS badFile => RETURN[FALSE]; END; NextItem: PUBLIC PROCEDURE RETURNS[name, args: STRING] = BEGIN terminator: CHARACTER; localLine: STRING ← [lineSize]; name ← args ← NIL; terminator ← ReadLineOrToken[lastOpenedCFI.sh, localLine, ':]; IF terminator # ': THEN BEGIN [] ← ReadLineOrToken[lastOpenedCFI.sh, localLine, Ascii.CR]; RETURN[NIL,NIL]; END; name ← Storage.CopyString[localLine]; [] ← ReadLineOrToken[lastOpenedCFI.sh, localLine, Ascii.CR]; args ← IF localLine.length = 0 THEN NIL ELSE Storage.CopyString[localLine]; RETURN[name, args]; END; -- Use "User.cm" as fileName in above UserDotCmOpen: PUBLIC PROCEDURE = BEGIN Open[userDotCmName]; END; UserDotCmClose: PUBLIC PROCEDURE = BEGIN Close[userDotCmName]; END; UserDotCmLine: PUBLIC PROCEDURE [title, name: STRING] RETURNS [STRING] = BEGIN RETURN[Line[fileName: userDotCmName, title: title, name: name]]; END; UserDotCmOpenSection: PUBLIC PROCEDURE [title: STRING] RETURNS [BOOLEAN] = BEGIN RETURN[OpenSection[userDotCmName, title]]; END; UserDotCmNextItem: PUBLIC PROCEDURE RETURNS[name, args: STRING] = BEGIN -- Support pulling entries out of two files at the same time since it is -- not too hard and some clown will probably try it. saveCFI: CmFileInfoHandle ← lastOpenedCFI; lastOpenedCFI ← @userDotCmInfo; [name, args] ← NextItem[]; lastOpenedCFI ← saveCFI; RETURN[name, args]; END; -- Private Procedures FileClose: PRIVATE PROCEDURE [fileName: STRING] = BEGIN IF lastOpenedCFI = NIL THEN {SIGNAL Error[noneOpen]; RETURN}; IF ~String.EquivalentString[fileName, lastOpenedCFI.fileName] THEN SIGNAL Error[notOpen]; IF lastOpenedCFI.sh # NIL THEN Streams.Destroy[lastOpenedCFI.sh]; -- There are other pointers to lastOpenedCFI↑, so NIL the freed pointers! lastOpenedCFI.fh ← NIL; lastOpenedCFI.sh ← NIL; END; FileOpen: PRIVATE PROCEDURE [fileName: STRING] = BEGIN isUserDotCm: BOOLEAN = IsUserDotCm[fileName]; IF isUserDotCm THEN lastOpenedCFI ← @userDotCmInfo ELSE BEGIN lastOpenedCFI ← @otherCmInfo; IF ~String.EquivalentString[fileName, lastOpenedCFI.fileName] THEN BEGIN IF lastOpenedCFI.keepOpen THEN BEGIN SIGNAL Error[multipleOpens]; FileClose[lastOpenedCFI.fileName]; lastOpenedCFI.keepOpen ← FALSE; END; lastOpenedCFI.fileName ← Storage.FreeStringNil[lastOpenedCFI.fileName]; lastOpenedCFI.fh ← NIL; -- Different file => bad FHandle END; END; IF lastOpenedCFI.keepOpen THEN {Streams.SetIndex[lastOpenedCFI.sh, 0]; RETURN}; IF lastOpenedCFI.fh = NIL THEN lastOpenedCFI.fh ← Segments.NewFile[fileName, Segments.Read]; lastOpenedCFI.sh ← Streams.CreateStream[lastOpenedCFI.fh, Segments.Read ! Segments.FileUnknown[] => BEGIN Segments.ReleaseFile[lastOpenedCFI.fh]; lastOpenedCFI.fh ← Segments.NewFile[fileName, Segments.Read]; RETRY; END]; IF lastOpenedCFI.fileName = NIL THEN lastOpenedCFI.fileName ← Storage.CopyString[fileName]; END; FindLineGivenSection: PRIVATE PROCEDURE [localLine, name: STRING] RETURNS [BOOLEAN] = BEGIN DO [] ← ReadLineOrToken[lastOpenedCFI.sh, localLine, Ascii.CR]; IF localLine.length = 0 THEN RETURN[FALSE]; IF localLine[0]='[ THEN RETURN[FALSE]; IF NameMatch[localLine, name] THEN RETURN[TRUE]; ENDLOOP; END; FindSection: PRIVATE PROCEDURE [title: STRING] RETURNS [BOOLEAN] = BEGIN temp: STRING ← [lineSize]; DO IF ReadLineOrToken[lastOpenedCFI.sh, temp, Ascii.CR] = Ascii.NUL THEN RETURN[FALSE]; IF temp.length # 0 AND TitleMatch[temp, title] THEN RETURN[TRUE]; ENDLOOP; END; IsUserDotCm: PRIVATE PROCEDURE [fileName: STRING] RETURNS [b: BOOLEAN] = BEGIN oldLength: CARDINAL = fileName.length; IF fileName[fileName.length-1] = '. THEN fileName.length ← fileName.length - 1; b ← String.EquivalentString[fileName, userDotCmName]; fileName.length ← oldLength; END; NameMatch: PRIVATE PROCEDURE [localLine, name: STRING] RETURNS [BOOLEAN] = BEGIN llSubString, nameSubString: String.SubStringDescriptor; IF localLine.length < name.length+2 THEN RETURN[FALSE]; IF localLine[name.length] # ': THEN RETURN[FALSE]; IF localLine[name.length+1] # Ascii.SP THEN RETURN[FALSE]; llSubString ← [localLine, 0, name.length]; nameSubString ← [name, 0, name.length]; RETURN[String.EquivalentSubString[@llSubString, @nameSubString]] END; OpenAndMatch: PRIVATE PROCEDURE [localLine, fileName, title, name: STRING] RETURNS [BOOLEAN] = INLINE BEGIN FileOpen[fileName ! Segments.FileNameError => GOTO badFile; Error => RESUME]; RETURN[(FindSection[title] AND FindLineGivenSection[localLine, name])]; EXITS badFile => RETURN[FALSE]; END; RealGetNextToken: PRIVATE PROCEDURE [ source: String.SubString, token: STRING, number: BOOLEAN] RETURNS [valid: BOOLEAN] = BEGIN started: BOOLEAN ← FALSE; c: CHARACTER; token.length ← 0; DO IF source.length = 0 THEN EXIT; SELECT c ← CmFile.Lop[source] FROM Ascii.SP, Ascii.TAB => IF started THEN EXIT; Ascii.CR, ': => EXIT; '-, IN ['0..'9], 'B, 'D => BEGIN IF token.length < token.maxlength THEN {token[token.length] ← c; token.length ← token.length + 1}; started ← TRUE; END; ENDCASE => BEGIN IF number THEN EXIT; IF token.length < token.maxlength THEN {token[token.length] ← c; token.length ← token.length + 1}; started ← TRUE; END; ENDLOOP; RETURN[token.length # 0]; END; END. -- of CmFilesA