FileParmPack.mesa
Copyright © 1985 by Xerox Corporation. All rights reserved.
Satterthwaite, March 25, 1986 4:14:50 pm PST
Paul Rovner, November 21, 1983 11:43 am
Russ Atkinson (RRA) March 6, 1985 11:01:31 pm PST
DIRECTORY
BcdDefs: TYPE USING [Base, BcdBase, Link, MTIndex, MTRecord, NameString, SGIndex, FTSelf, SGNull, VersionID, VersionStamp],
CommandUtil: TYPE USING [PairList, KeyValue],
ConvertUnsafe: TYPE USING [EqualSubStrings, SubString],
FS: TYPE USING [Create, Close, Error, Read, nullOpenFile, Open, OpenFile],
FileParms: TYPE USING [ActualId, BindingProc, Name, Ops, SymbolSpace, nullActual, nullSymbolSpace],
FileParmOps: TYPE USING [],
FileSegment: TYPE USING [Span, nullSpan],
Rope: TYPE USING [Concat, Equal, Find, Flatten, Length, ROPE],
VM: TYPE USING [Allocate, Free, Interval, nullInterval, AddressForPageNumber];
FileParmPack: PROGRAM
IMPORTS CommandUtil, ConvertUnsafe, FS, Rope, VM
EXPORTS FileParmOps = {
Name: TYPE = FileParms.Name;
ActualId: TYPE = FileParms.ActualId;
nullActual: ActualId = FileParms.nullActual;
FileIndex: TYPE = NAT;
nullFileIndex: FileIndex = FileIndex.LAST;
primary operations for read-only access
Binding: PROC[
formalId, formalType: Name, defaultLocator: Name, binder: FileParms.BindingProc] = {
i: FileIndex;
name: Rope.ROPE = FileName[formalId, defaultLocator];
type: Rope.ROPE = formalType;
IF name = NIL THEN i ← nullFileIndex
ELSE {
file: FS.OpenFile ← FS.nullOpenFile;
FOR i IN [0 .. nextFile) DO
IF name.Equal[fileTable[i].name, FALSE] THEN {
IF type.Equal[fileTable[i].type, FALSE] THEN GO TO found;
file ← fileTable[i].file};
REPEAT
found => NULL;
FINISHED => {
matched: BOOL = (file # FS.nullOpenFile);
IF ~matched THEN
file ← FS.Open[name, $read ! FS.Error => TRUSTED {CONTINUE}];
IF file = FS.nullOpenFile THEN i ← nullFileIndex
ELSE {
version: BcdDefs.VersionStamp;
span: FileSegment.Span;
[version, span] ← ReadHeader[file, formalType];
i ← SearchCache[version];
IF i = nullFileIndex AND version # nullActual.version THEN {
i ← NewCacheEntry[];
fileTable[i] ← [
version: version, file: file, span: span, name: name, type: type];
}
ELSE IF ~matched THEN {file.Close[]; file ← FS.nullOpenFile}};
};
ENDLOOP;
IF i = nullFileIndex THEN binder[nullActual]
ELSE binder[[fileTable[i].version, fileTable[i].name]]}};
Acquire: PROC[id: Name, actual: ActualId] RETURNS[FileParms.SymbolSpace] = {
i: FileIndex ← SearchCache[actual.version];
IF i = nullFileIndex THEN {
i ← NewCacheEntry[];
fileTable[i] ← [version: actual.version, name: actual.locator, type: id]};
OpenFile[i];
RETURN[IF fileTable[i].file = FS.nullOpenFile OR fileTable[i].span = nullSpan
THEN FileParms.nullSymbolSpace
ELSE [file: fileTable[i].file, span: fileTable[i].span]]
};
Release: PROC[s: FileParms.SymbolSpace] = {NULL}; -- add ref counts?
Forget: PROC[actual: ActualId] = {
i: NAT ← 0;
WHILE i < nextFile DO {
IF fileTable[i].version = actual.version THEN GO TO delete;
IF fileTable[i].name # NIL THEN {
IF (fileTable[i].name).Equal[actual.locator, FALSE] THEN GO TO delete};
i ← i + 1;
EXITS
delete => {
ClearCacheEntry[i];
nextFile ← nextFile - 1;
IF i # nextFile THEN {
fileTable[i] ← fileTable[nextFile]; fileTable[nextFile] ← [nullActual.version]}}};
ENDLOOP
};
operations for update access
outputFile: FS.OpenFile;
AcquireOutput: PUBLIC PROC[name: Rope.ROPE] RETURNS[FS.OpenFile] = {
outputFile ← FS.Create[name];
RETURN[outputFile]};
ReleaseOutput: PUBLIC PROC[file: FS.OpenFile] = {
IF outputFile = FS.nullOpenFile OR ~FS.SameFile[file, outputFile] THEN ERROR;
file.Close[ ! FS.Error => CONTINUE]; outputFile ← FS.nullOpenFile};
command line arguments
aList: CommandUtil.PairList;
SetAList: PUBLIC PROC [map: CommandUtil.PairList] = {aList ← map};
ClearAList: PUBLIC PROC = {aList ← NIL};
initialization/finalization
Initialize: PUBLIC PROC RETURNS[FileParms.Ops] = {
fileTable ← NIL; AdjustFileTable[16];
nextFile ← 0;
outputFile ← FS.nullOpenFile;
RETURN[[Binding, Acquire, Release, Forget]]};
Finalize: PUBLIC PROC = {
FOR i: NAT IN [0..nextFile) DO ClearCacheEntry[i] ENDLOOP;
IF outputFile # FS.nullOpenFile THEN {
outputFile.Close[]; outputFile ← FS.nullOpenFile};
fileTable ← NIL};
interpretation of file names (Pilot PreCascade conventions)
FileName: PROC[key: Name, default: Name] RETURNS[Rope.ROPE] = {
t: Rope.ROPE ← CommandUtil.KeyValue[key, aList];
IF t = NIL THEN t ← default;
IF t = NIL THEN t ← key;
RETURN[NormalizeFileName[t]]};
NormalizeFileName: PROC[formal: Name] RETURNS[s: Rope.ROPE] = INLINE {
IF formal.Equal["$"] THEN s ← NIL
ELSE {
dotIndex: INT = formal.Find["."];
s ← IF dotIndex < 0 THEN formal.Concat[".bcd"] ELSE formal};
RETURN};
file setup
OpenFile: PROC [i: FileIndex] = {
IF fileTable[i].file = FS.nullOpenFile AND fileTable[i].name # NIL THEN
fileTable[i].file ← FS.Open[fileTable[i].name ! FS.Error => TRUSTED {CONTINUE}];
IF fileTable[i].file # FS.nullOpenFile AND fileTable[i].span = nullSpan THEN {
version: BcdDefs.VersionStamp;
[version, fileTable[i].span] ← ReadHeader[fileTable[i].file, fileTable[i].type];
IF version # fileTable[i].version THEN {
ClearCacheEntry[i];
fileTable[i].file ← FS.nullOpenFile; fileTable[i].span ← nullSpan}
}
};
low-level file manipulation and cache management
nullSpan: FileSegment.Span = FileSegment.nullSpan;
FileRecord: TYPE = RECORD[
version: BcdDefs.VersionStamp←,
file: FS.OpenFile←FS.nullOpenFile,
span: FileSegment.Span←nullSpan,
name: Name←NIL,
type: Name←NIL];
FileTable: TYPE = RECORD[SEQUENCE length: FileIndex OF FileRecord];
fileTable: REF FileTable;
nextFile: NAT;
file table management
SearchCache: PROC[version: BcdDefs.VersionStamp] RETURNS[i: FileIndex] = {
FOR i IN [0 .. nextFile) DO
IF fileTable[i].version = version THEN EXIT;
REPEAT
FINISHED => i ← nullFileIndex;
ENDLOOP;
RETURN};
NewCacheEntry: PROC RETURNS[i: FileIndex] = {
WHILE nextFile >= fileTable.length DO AdjustFileTable[fileTable.length + 16] ENDLOOP;
i ← nextFile; nextFile ← nextFile + 1};
AdjustFileTable: PROC [newSize: NAT] = {
newTable: REF FileTable;
oldSize: NAT = (IF fileTable = NIL THEN 0 ELSE fileTable.length);
IF newSize = 0 THEN newTable ← NIL
ELSE {
i: FileIndex;
newTable ← NEW[FileTable[newSize]];
FOR i IN [0..MIN[oldSize, newSize]) DO newTable[i] ← fileTable[i] ENDLOOP;
FOR i IN [oldSize..newSize) DO newTable[i] ← [version: nullActual.version] ENDLOOP};
fileTable ← newTable};
ClearCacheEntry: PROC[i: FileIndex] = {
IF fileTable[i].file # FS.nullOpenFile THEN {
(fileTable[i].file).Close[ ! FS.Error => IF error.code = $invalidOpenFile THEN CONTINUE];
fileTable[i].file ← FS.nullOpenFile};
fileTable[i].name ← fileTable[i].type ← NIL};
file setup
ReadHeader: PROC[file: FS.OpenFile, formalId: Name] RETURNS[
version: BcdDefs.VersionStamp ← nullActual.version,
span: FileSegment.Span ← nullSpan] = {
headerInterval: VM.Interval ← VM.nullInterval;
DeleteHeader: PROC = {
IF headerInterval # VM.nullInterval THEN {
headerInterval.Free[];
headerInterval ← VM.nullInterval}
};
IF file # FS.nullOpenFile THEN {
ENABLE {
UNWIND => {NULL};
ANY => {GO TO badFile}};
BcdBase: PROC [p: LONG POINTER] RETURNS [BcdDefs.Base] = INLINE {
RETURN [LOOPHOLE[p, BcdDefs.Base]]};
bcd: BcdDefs.BcdBase;
bcdPages: CARDINAL ← 1;
mtb, ftb, sgb: BcdDefs.Base;
mti: BcdDefs.MTIndex;
sSeg: BcdDefs.SGIndex;
nString: BcdDefs.NameString;
typeId: ConvertUnsafe.SubString;
d: ConvertUnsafe.SubString;
DO
headerInterval ← VM.Allocate[count: bcdPages];
bcd ← VM.AddressForPageNumber[headerInterval.page];
file.Read[from: 0, nPages: bcdPages, to: bcd];
IF bcd.versionIdent # BcdDefs.VersionID THEN GOTO badFile;
IF bcdPages >= bcd.nPages THEN EXIT;
bcdPages ← bcd.nPages;
headerInterval.Free[]; headerInterval ← VM.nullInterval
ENDLOOP;
IF bcd.nConfigs # 0 THEN GOTO badFile; -- no packaged bcd's (for now)
nString ← LOOPHOLE[bcd + bcd.ssOffset];
typeId.base ← LOOPHOLE[formalId.Flatten[]];
typeId.offset ← 0;
typeId.length ← formalId.Length[];
d.base ← @nString.string;
ftb ← BcdBase[bcd + bcd.ftOffset];
mtb ← BcdBase[bcd + bcd.mtOffset]; mti ← BcdDefs.MTIndex.FIRST;
UNTIL mti = bcd.mtLimit DO
d.offset ← mtb[mti].name; d.length ← nString.size[mtb[mti].name];
IF ConvertUnsafe.EqualSubStrings[typeId, d] THEN EXIT;
mti ← mti + (WITH m: mtb[mti] SELECT FROM
direct => BcdDefs.MTRecord.direct.SIZE + m.length*BcdDefs.Link.SIZE,
indirect => BcdDefs.MTRecord.indirect.SIZE,
multiple => BcdDefs.MTRecord.multiple.SIZE,
ENDCASE => ERROR);
REPEAT
FINISHED =>
IF bcd.nModules = 1 THEN mti ← BcdDefs.MTIndex.FIRST ELSE GOTO badFile;
ENDLOOP;
ftb ← BcdBase[bcd + bcd.ftOffset];
version ← IF mtb[mti].file = BcdDefs.FTSelf
THEN bcd.version
ELSE ftb[mtb[mti].file].version;
sgb ← BcdBase[bcd + bcd.sgOffset]; sSeg ← mtb[mti].sseg;
IF sSeg = BcdDefs.SGNull
OR sgb[sSeg].pages = 0 OR sgb[sSeg].file # BcdDefs.FTSelf THEN GO TO badFile;
span ← [base: sgb[sSeg].base - 1, pages: sgb[sSeg].pages];
DeleteHeader[];
EXITS
badFile => {DeleteHeader[]; span ← nullSpan}
};
RETURN};
}.