-- 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