-- file FileParmPack.mesa
-- last modified by Satterthwaite, December 10, 1982 12:28 pm
DIRECTORY
BcdDefs: TYPE USING [
Base, Link, MTIndex, MTRecord, SGIndex, VersionStamp, FTSelf, SGNull, VersionID],
BcdOps: TYPE USING [BcdBase, NameString],
CommandUtil: TYPE USING [PairList, FreePairList, KeyValue],
File: TYPE USING [Capability, nullCapability],
FileParms: TYPE USING [
ActualId, BindingProc, Name, Ops, SymbolSpace, nullActual],
FileParmOps: TYPE USING [],
FileSegment: TYPE USING [Pages, Span, nullPages],
OSMiscOps: TYPE USING [FindFile, FileError, UnnameFile],
Space: TYPE USING [
Handle, nullHandle, virtualMemory, Create, LongPointer, Map, Delete],
Strings: TYPE USING [
String, SubString, SubStringDescriptor,
AppendChar, AppendString, AppendSubString, EqualSubStrings, EquivalentSubStrings],
SymbolTable: TYPE USING [anySpan, Forget, Locked, SetCacheSize],
TimeStamp: TYPE USING [Stamp];
FileParmPack: PROGRAM
IMPORTS CommandUtil, OSMiscOps, Space, Strings, SymbolTable
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: Strings.String,
binder: FileParms.BindingProc] = {
i: FileIndex;
name: Strings.String ← FileName[@formalId, defaultLocator];
type: Strings.String ← CopyName[@formalType];
IF name = NIL THEN i ← nullFileIndex
ELSE {
file: File.Capability ← nullFile;
FOR i IN [0 .. nextFile) DO
IF EquivalentStrings[name, fileTable[i].name] THEN {
IF EquivalentStrings[type, fileTable[i].type] THEN GO TO found;
file ← fileTable[i].pages.file};
REPEAT
found => {zone.FREE[@name]; zone.FREE[@type]};
FINISHED => {
version: TimeStamp.Stamp;
span: FileSegment.Span;
IF file = nullFile THEN file ← CreateFile[name];
IF file = nullFile THEN i ← nullFileIndex
ELSE {
[version, span] ← ReadHeader[file, @formalType];
i ← SearchCache[version];
IF i = nullFileIndex AND version # nullActual.version THEN {
i ← NewCacheEntry[];
fileTable[i] ← [
version: version, pages: [file, span], name: name, type: type]}
ELSE {
zone.FREE[@name]; zone.FREE[@type]}}};
ENDLOOP;
IF i = nullFileIndex THEN binder[nullActual]
ELSE binder[[fileTable[i].version, [fileTable[i].name, 0, fileTable[i].name.length]]]}};
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: CopyName[@actual.locator], type: CopyName[@id]]};
OpenFile[i];
RETURN [IF fileTable[i].pages.file = nullFile OR fileTable[i].pages.span = nullSpan
THEN nullPages
ELSE fileTable[i].pages]};
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 {
d: Strings.SubStringDescriptor ← [fileTable[i].name, 0, fileTable[i].name.length];
IF Strings.EquivalentSubStrings[@d, @actual.locator] 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
AcquireOutput: PUBLIC PROC [name: Strings.String] RETURNS [file: File.Capability] = {
file ← OSMiscOps.FindFile[name, $write
! OSMiscOps.FileError => {file ← File.nullCapability; CONTINUE}];
IF file # File.nullCapability THEN {
SymbolTable.Forget[[file: file, span: SymbolTable.anySpan]
! SymbolTable.Locked => {GOTO MakeTemporary}];
EXITS
MakeTemporary => {
OSMiscOps.UnnameFile[name, file];
file ← OSMiscOps.FindFile[name, $write]}};
RETURN};
ReleaseOutput: PUBLIC PROC [file: File.Capability] = {NULL};
-- command line arguments
aList: CommandUtil.PairList;
SetAList: PUBLIC PROC [map: CommandUtil.PairList] = {aList ← map};
ClearAList: PUBLIC PROC = {aList ← CommandUtil.FreePairList[aList]};
-- initialization/finalization
Initialize: PUBLIC PROC [scratchZone: UNCOUNTED ZONE] RETURNS [FileParms.Ops] = {
zone ← scratchZone;
fileTable ← NIL; AdjustFileTable[16];
nextFile ← 0;
SymbolTable.SetCacheSize[256];
RETURN [[Binding, Acquire, Release, Forget]]};
Finalize: PUBLIC PROC = {
SymbolTable.SetCacheSize[0];
FOR i: NAT IN [0..nextFile) DO ClearCacheEntry[i] ENDLOOP;
zone.FREE[@fileTable]; zone ← NIL};
-- interpretation of file names (Pilot PreCascade conventions)
FileName: PROC [key: Strings.SubString, default: Strings.String] RETURNS [Strings.String] = {
t: Strings.String = CommandUtil.KeyValue[key, aList];
d: Strings.SubStringDescriptor ← SELECT TRUE FROM
(t # NIL) => [base: t, offset: 0, length: t.length],
(default # NIL) => [base: default, offset: 0, length: default.length],
ENDCASE => key↑;
RETURN [NormalizeFileName[@d]]};
CopyName: PROC [master: Strings.SubString] RETURNS [s: Strings.String] = {
s ← zone.NEW[StringBody[master.length]];
Strings.AppendSubString[s, master];
RETURN};
NormalizeFileName: PROC [formal: Strings.SubString] RETURNS [s: Strings.String] = {
IF formal.length = 1 AND formal.base[formal.offset] = '$ THEN s ← NIL
ELSE {
char: CHAR;
dot: BOOL ← FALSE;
s ← zone.NEW[StringBody[formal.length+(".bcd"L).length]];
FOR i: CARDINAL IN [formal.offset .. formal.offset+formal.length) DO
char ← formal.base[i];
IF char = '. THEN dot ← TRUE;
Strings.AppendChar[s, char];
ENDLOOP;
IF ~dot THEN Strings.AppendString[s, ".bcd"L]};
RETURN};
EquivalentStrings: PROC [s1, s2: Strings.String] RETURNS [BOOL] = {
IF s1 # NIL AND s2 # NIL THEN {
d1: Strings.SubStringDescriptor ← [base: s1, offset: 0, length: s1.length];
d2: Strings.SubStringDescriptor ← [base: s2, offset: 0, length: s2.length];
RETURN [Strings.EquivalentSubStrings[@d1, @d2]]}
ELSE RETURN [FALSE]};
-- file setup
OpenFile: PROC [i: FileIndex] = {
IF fileTable[i].pages.file = nullFile AND fileTable[i].name # NIL THEN
fileTable[i].pages.file ← CreateFile[fileTable[i].name];
IF fileTable[i].pages.file # nullFile AND fileTable[i].pages.span = nullSpan THEN {
version: TimeStamp.Stamp;
d: Strings.SubStringDescriptor ← [fileTable[i].type, 0, fileTable[i].type.length];
[version, fileTable[i].pages.span] ← ReadHeader[fileTable[i].pages.file, @d];
IF version # fileTable[i].version THEN {
ClearCacheEntry[i];
fileTable[i].pages ← nullPages}}};
-- low-level file manipulation and cache management
zone: UNCOUNTED ZONE ← NIL;
nullPages: FileSegment.Pages = FileSegment.nullPages;
nullFile: File.Capability = nullPages.file;
nullSpan: FileSegment.Span = nullPages.span;
FileRecord: TYPE = RECORD[
version: TimeStamp.Stamp ← ,
pages: FileSegment.Pages ← nullPages,
name: Strings.String ← NIL,
type: Strings.String ← NIL];
FileTable: TYPE = RECORD [SEQUENCE length: FileIndex OF FileRecord];
fileTable: LONG POINTER TO FileTable;
nextFile: NAT;
-- file table management
SearchCache: PROC [version: TimeStamp.Stamp] 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: LONG POINTER TO FileTable;
oldSize: NAT = IF fileTable = NIL THEN 0 ELSE fileTable.length;
IF newSize = 0 THEN newTable ← NIL
ELSE {
i: FileIndex;
newTable ← zone.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};
IF fileTable # NIL THEN zone.FREE[@fileTable];
fileTable ← newTable};
ClearCacheEntry: PROC [i: FileIndex] = {
IF fileTable[i].name # NIL THEN zone.FREE[@fileTable[i].name];
IF fileTable[i].type # NIL THEN zone.FREE[@fileTable[i].type]};
-- file setup
CreateFile: PROC [s: Strings.String] RETURNS [file: File.Capability ← nullPages.file] = {
IF s # NIL THEN {
oldLength: CARDINAL = s.length;
IF oldLength > 1 AND s[s.length-1] = '.
THEN s.length ← s.length - 1; -- undo Alto convention for Pilot
file ← OSMiscOps.FindFile[s, $read ! OSMiscOps.FileError => {CONTINUE}];
s.length ← oldLength};
RETURN};
ReadHeader: PROC [file: File.Capability, typeId: Strings.SubString] RETURNS [
version: TimeStamp.Stamp ← nullActual.version,
span: FileSegment.Span ← nullSpan] = {
headerSpace: Space.Handle ← Space.nullHandle;
DeleteHeader: PROC = {
IF headerSpace # Space.nullHandle THEN {
Space.Delete[headerSpace];
headerSpace ← Space.nullHandle}};
IF file # File.nullCapability THEN {
ENABLE {
UNWIND => {NULL};
ANY => {GO TO badFile}};
BcdBase: PROC [p: LONG POINTER] RETURNS [BcdDefs.Base] = INLINE {
RETURN [LOOPHOLE[p, BcdDefs.Base]]};
bcd: BcdOps.BcdBase;
bcdPages: CARDINAL ← 8;
mtb, ftb, sgb: BcdDefs.Base;
mti: BcdDefs.MTIndex;
sSeg: BcdDefs.SGIndex;
nString: BcdOps.NameString;
d: Strings.SubStringDescriptor;
DO
headerSpace ← Space.Create[size: bcdPages, parent: Space.virtualMemory];
headerSpace.Map[window: [file: file, base: 1]];
bcd ← headerSpace.LongPointer[];
IF bcd.versionIdent # BcdDefs.VersionID THEN GO TO badFile;
IF bcdPages >= bcd.nPages THEN EXIT;
bcdPages ← bcd.nPages;
Space.Delete[headerSpace]; headerSpace ← Space.nullHandle
ENDLOOP;
IF bcd.nConfigs # 0 THEN GO TO badFile; -- no packaged bcd's (for now)
nString ← LOOPHOLE[bcd + bcd.ssOffset];
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 Strings.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, pages: sgb[sSeg].pages];
DeleteHeader[];
EXITS
badFile => {DeleteHeader[]; span ← nullSpan}};
RETURN};
}.