Copyright Ó 1981, 1982, 1984 , 1992by Xerox Corporation. All rights reserved.
File: CmFilesA.mesa - last edit:
SXW , 10-Jul-81 17:46:35
BXM , 16-Jul-81 16:28:10
JGS,   2-Sep-81 9:47:44
Tom,  Aug 28, 1979 6:01 PM
Mark, 15-Jul-81 11:02:48
PXK ,  8-Jan-82 18:20:19
RXR , 13-Oct-81 16:02:24
SXS , 14-Oct-81 10:09:17
LXR ,  4-Jan-84 10:55:39
AXD , 21-Sep-82 16:17:38
CXH , 16-Dec-82 17:30:57
Philip James, March 13, 1991 4:51 pm PST
Christian Jacobi, April 7, 1992 5:21 pm PDT
DIRECTORY
ARAccess USING [AppendChar],
Ascii USING [CR, LF, NUL, SP, TAB],
CmFile USING [ErrorCode, Handle, noMatch, TableDesc],
Event: TYPE USING [fileSystem],
EventTypes: TYPE USING [newSearchPath],
Heap: TYPE USING [Create, Flush, systemZone],
IO USING [Close, EndOfStream, GetChar, GetIndex, SetIndex, STREAM],
MFile USING
[dontRelease, Error, GetProperties, Handle, maxNameLength, ReadOnly, Release],
MStream USING [Create, Error, GetFile, Handle, PleaseReleaseProc, ReadOnly],
PFS USING [Close, Error, Open, OpenFile, PathFromRope, StreamFromOpenFile],
Rope USING [Compare, Concat, Equal, Fetch, IsEmpty, Length, ROPE, Substr],
Stream USING [EndOfStream, GetChar, GetPosition, Handle, SetPosition],
String USING [
AppendChar, AppendString, Compare, CopyToNewString, EquivalentString,
EquivalentSubString, StringBoundsFault, SubStringDescriptor],
StringLookUp USING [InTable, noMatch, TableDesc],
Supervisor USING [
AddDependency, AgentProcedure, CreateSubsystem, SubsystemHandle],
System USING [switches],
Token USING [
Filtered, FilterProcType, GetCharProcType, Handle, Line,
nonQuote, Object, QuoteProcType--, WhiteSpace--];
Volume USING [GetLabelString, systemID];
CmFilesA: CEDAR MONITOR
IMPORTS
ARAccess, --Event, Heap, --IO, --MFile, MStream, --PFS, Rope--, Stream, String, StringLookUp,
--Supervisor, System--, Token--, Volume
EXPORTS CmFile =
BEGIN
Global Data
lineSize: CARDINAL = 250;
noSection: LONG CARDINAL = LAST[LONG CARDINAL];
agent: Supervisor.SubsystemHandle ← Supervisor.CreateSubsystem[FileSystemEvent];
sectionHints: HintHandle ¬ NIL;
userCm: IO.STREAM ¬ NIL; --MStream.Handle ¬ NIL;
readingCount: CARDINAL ¬ 0;
resetStream: BOOLEAN ¬ FALSE;
z: UNCOUNTED ZONE = Heap.Create[initial: 2, increment: 1];
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;
Syntax of a Cm file is:
CmFile ::= Section | CmFile Section
Section ::= SectionName SectionBody
SectionName ::= beginSectionName NameBody endSectionName
NameBody ::= NameBody ~endSectionName | ~endSectionName
SectionBody ::= SectionBody SectionLine | SectionLine |
SectionLine ::= Name nameBreak MiscLine | MiscLine
Name ::= Name ~nameBreak | ~nameBreak
MiscLines ::= MiscLines MiscLine | MiscLine |
Any line that begins with two commentChar's is ignored, as is any
SectionLine that is only a MiscLine.
beginSectionName: CHARACTER = '[;
commentChar: CHARACTER = '-;
endSectionName: CHARACTER = '];
nameBreak: CHARACTER = ':;
separateSectionName: CHARACTER = ':;
SIGNALs
Error: PUBLIC SIGNAL [code: CmFile.ErrorCode] = CODE;
TableError: PUBLIC SIGNAL [h: CmFile.Handle, name: Rope.ROPE] = CODE;
From StringLookUpsA
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] =
Input restrictions: key # NIL AND entry # NIL AND maxLength > 0
AND key.length >= 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] =
If table has duplicate entries, only the first one's index might be returned
{
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;
};
Simple Interface Procedures
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
Heap.systemZone.FREE[@h];
RETURN[h]};
Line: PUBLIC PROCEDURE [fileName, title, name: Rope.ROPE]
RETURNS [s: Rope.ROPE] =
This is not as quick as it could be, but minimizes code.
Note that NextItem will automatically go from generic to specific section.
BEGIN
h: CmFile.Handle;
s ← NIL;
h ← Open[fileName ! Error => GOTO NoFile];
IF FindSection[h, title] THEN
DO
tag, value: Rope.ROPE;
[tag, value] ← NextItem[h ! UNWIND => h ← Close[h]];
IF tag = NIL THEN EXIT;
IF String.EquivalentString[name, tag] THEN {
temp: Rope.ROPE ← s; s ← value; value ← temp};
Heap.systemZone.FREE[@tag];
Heap.systemZone.FREE[@value];
ENDLOOP;
h ← Close[h];
EXITS NoFile => NULL;
END;
Open: PUBLIC PROCEDURE [fileName: Rope.ROPE] RETURNS [h: CmFile.Handle] =
BEGIN
repH: Handle;
BEGIN
fh: PFS.OpenFile; --MFile.Handle;
sh: IO.STREAM; --Stream.Handle;
IF System.switches['N] = down AND
String.EquivalentString[fileName, "User.cm"L] THEN GOTO Lose;
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
Output assertion: h.break is one of terminator, Ascii.CR, or Ascii.NUL
BEGIN
buffer ¬ NIL; --buffer.length ¬ 0;
Skip over leading white space
WHILE
SELECT (h.break ¬ h.getChar[h]) FROM
Ascii.SP, Ascii.TAB => TRUE,
ENDCASE => FALSE DO ENDLOOP;
Now pick up token (or the line)
DO
SELECT h.break FROM
terminator, Ascii.CR, Ascii.NUL => EXIT;
ENDCASE;
buffer ¬ ARAccess.AppendChar[buffer, h.break];
String.AppendChar[
buffer, h.break ! String.StringBoundsFault => GOTO TooMany];
h.break ¬ h.getChar[h];
ENDLOOP;
EXITS TooMany => 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;
More efficient ones
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;
Volume.GetLabelString[Volume.systemID, localLine];
localLine ¬ ARAccess.AppendChar[localLine, separateSectionName];
String.AppendChar[localLine, separateSectionName];
localLine ¬ localLine.Concat[title];
String.AppendString[localLine, title];
qualifiedTitle ¬ localLine;
qualifiedTitle ← String.CopyToNewString[localLine, Heap.systemZone];
First try to locate both generic and specific sections
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;
CheckHints[];
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;
DoneReading[]
}
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};
Heap.systemZone.FREE[@qualifiedTitle];
Process generic section, then specific section
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;
name ← String.CopyToNewString[localLine, Heap.systemZone];
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];
value.length ← 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;
Use "User.cm" as fileName in above
UserDotCmOpen: PUBLIC PROCEDURE RETURNS [h: CmFile.Handle] =
BEGIN h ← Open["User.cm"]; END;
UserDotCmLine: PUBLIC PROCEDURE [title, name: Rope.ROPE]
RETURNS [s: Rope.ROPE] =
BEGIN s ← Line[fileName: "User.cm", title: title, name: name]; END;
Private Procedures
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];
name: Rope.ROPENIL; --[MFile.maxNameLength];
[] ← MFile.GetProperties[MStream.GetFile[repH.sh], name];
RETURN[Rope.Equal[name, "User.cm"]];
END;
ReadLine: PRIVATE PROCEDURE [h: CmFile.Handle, buffer: Rope.ROPE] =
Suppresses comment lines
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] =
Suppresses comment lines and any line that is neither a SectionName
nor a SectionLine
Output assertion: h.break = Ascii.NUL OR h.break = nameBreak OR
h.getChar is positioned just after Ascii.CR
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;
CheckHints: ENTRY PROCEDURE =
BEGIN
localLine: Rope.ROPE ← [lineSize];
list, l: HintHandle ← NIL;
position: LONG CARDINAL;
name: Rope.ROPENIL;
index: CARDINAL ← 0;
tokenHandle: Token.Object ← [getChar, Ascii.NUL];
getChar: Token.GetCharProcType = {
c ← Stream.GetChar[userCm ! Stream.EndOfStream => {c ← Ascii.NUL; CONTINUE}]};
IF sectionHints # NIL THEN {readingCount ← readingCount + 1; RETURN};
userCm ← MStream.ReadOnly["User.cm"L, [myRelease, NIL] ! MStream.Error => CONTINUE];
IF userCm = NIL THEN {SIGNAL Error[fileNotFound]; RETURN};
DO
ReadLine[@tokenHandle, localLine];
IF tokenHandle.break = Ascii.NUL THEN EXIT;
IF localLine.length = 0 OR localLine[0] # beginSectionName THEN LOOP;
position ← Stream.GetPosition[userCm];
FOR index IN [1..localLine.length) DO
IF localLine[index] = endSectionName THEN EXIT;
REPEAT FINISHED => LOOP;
ENDLOOP;
name ← z.NEW[StringBody[index - 1]];
FOR i: CARDINAL IN [1..index) DO name[i - 1] ← localLine[i] ENDLOOP;
name.length ← index - 1;
IF list = NIL THEN list ← z.NEW[HintObject ← [name, position, list]]
ELSE SELECT String.Compare[name, list.name] FROM
-1 => list ← z.NEW[HintObject ← [name, position, list]];
0 => z.FREE[@name];
ENDCASE => {
FOR l ← list, l.next UNTIL l.next = NIL DO
SELECT String.Compare[name, l.next.name] FROM
-1 => EXIT;
0 => {z.FREE[@name]; EXIT};
ENDCASE;
ENDLOOP;
IF name # NIL THEN
l.next ← z.NEW[HintObject ← [name, position, l.next]]};
ENDLOOP;
sectionHints ← list;
readingCount ← readingCount + 1;
END;
DoneReading: ENTRY PROCEDURE =
BEGIN
readingCount ← readingCount - 1;
IF readingCount = 0 AND resetStream THEN
{FreeList[TRUE]; resetStream ← FALSE}
END;
myRelease: ENTRY MStream.PleaseReleaseProc =
BEGIN
IF readingCount > 0 THEN RETURN[no];
FreeList[FALSE];
RETURN[goAhead]
END;
FileSystemEvent: ENTRY Supervisor.AgentProcedure =
BEGIN
IF event # EventTypes.newSearchPath THEN RETURN;
IF readingCount > 0 THEN resetStream ← TRUE
ELSE FreeList[TRUE];
END;
FreeList: INTERNAL PROCEDURE [freeStream: BOOLEAN] =
BEGIN
Heap.Flush[z];
sectionHints ¬ NIL;
IF freeStream AND userCm # NIL THEN userCm.Close--delete--[--userCm--];
userCm ¬ NIL;
END;
main body
Supervisor.AddDependency[client: agent, implementor: Event.fileSystem];
END. -- of CmFilesA