-- 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}; }.