-- file FilePack.Mesa
-- last modified by Satterthwaite, October 19, 1979  10:42 AM

DIRECTORY
  AltoFileDefs: FROM "altofiledefs" USING [FP],
  BcdDefs: FROM "bcddefs"
    USING [
      BCD, MTIndex, MTRecord, NameString, SGIndex, VersionStamp,
      FTSelf, SGNull, VersionID],
  Copier: FROM "copier",
  DirectoryDefs: FROM "directorydefs" USING [EnumerateDirectory],
  SegmentDefs: FROM "segmentdefs"
    USING [
      FileHandle, FileSegmentHandle, OldFileOnly, Read,
      DeleteFileSegment, FileError, FileNameError, FileSegmentAddress,
      InsertFile, MoveFileSegment, NewFile, NewFileSegment, ReleaseFile,
      SwapIn, SwapOut, Unlock],
  StringDefs: FROM "stringdefs"
    USING [
      SubStringDescriptor,
      AppendChar, AppendString, AppendSubString,
      EqualSubStrings, EquivalentSubStrings],
  SymbolTable: FROM "symboltable"
    USING [
      Base, Handle, NullHandle,
      Acquire, Release, SegmentForTable, SetCacheSize, TableForSegment],
  Symbols: FROM "symbols"
    USING [mdType,
      HTIndex, MDRecord, MDIndex, FileIndex,
      HTNull, CTXNull, IncludedCTXNull, OwnMdi, MDNull, NullFileIndex],
  SymbolOps: FROM "symbolops" USING [EnterString, SubStringForHash],
  SymbolSegment: FROM "symbolsegment" USING [VersionID],
  SystemDefs: FROM "systemdefs"
    USING [
      AllocateHeapNode, AllocateHeapString, FreeHeapNode, FreeHeapString],
  Table: FROM "table"
    USING [Base, Notifier, AddNotify, Allocate, Bounds, DropNotify];

FilePack: PROGRAM
    IMPORTS
	DirectoryDefs, SegmentDefs, StringDefs, SymbolTable, SymbolOps,
	SystemDefs, Table 
    EXPORTS Copier = 
  BEGIN
  OPEN Symbols;

  SubStringDescriptor: TYPE = StringDefs.SubStringDescriptor;

-- tables defining the current symbol table

  mdb: Table.Base;		-- module directory base

  FilePackNotify: Table.Notifier =
    BEGIN  -- called whenever the main symbol table is repacked
    mdb ← base[mdType];
    END;
  

-- included module accounting

  VersionStamp: TYPE = BcdDefs.VersionStamp;

  FileProblem: PUBLIC SIGNAL [HTIndex] RETURNS [BOOLEAN] = CODE;
  FileVersion: PUBLIC SIGNAL [HTIndex] RETURNS [BOOLEAN] = CODE;
  FileVersionMix: PUBLIC SIGNAL [HTIndex] = CODE;

  AnyVersion: VersionStamp = [net:0, host:0, time:0];

  EqStamps: PROCEDURE [v1, v2: POINTER TO VersionStamp] RETURNS [BOOLEAN] =
    BEGIN
    RETURN [v1.time = v2.time AND v1.net = v2.net AND v1.host = v2.host]
    END;


  EnterFile: PUBLIC PROCEDURE [id: HTIndex, name: STRING] RETURNS [HTIndex] =
    BEGIN
    mdi: MDIndex = FindMdEntry[id, AnyVersion, NormalizeFileName[name]];
    RETURN [mdb[mdi].moduleId]
    END;

  NormalizeFileName: PROCEDURE [name: STRING] RETURNS [hti: HTIndex] =
    BEGIN
    i: CARDINAL;
    char: CHARACTER;
    dot: BOOLEAN ← FALSE;
    s: STRING ← SystemDefs.AllocateHeapString[name.length+(".bcd"L).length+1];
    desc: SubStringDescriptor;
    FOR i IN [0 .. name.length)
      DO
      SELECT (char ← name[i]) FROM
	IN ['A..'Z] =>  char ← char + ('a-'A);
	'. =>  dot ← TRUE;
	ENDCASE;
      StringDefs.AppendChar[s, char];
      ENDLOOP;
    IF ~dot THEN StringDefs.AppendString[s, ".bcd"L];
    IF s[s.length-1] # '. THEN StringDefs.AppendChar[s, '.];
    desc ← [base:s, offset:0, length: s.length];
    hti ← SymbolOps.EnterString[@desc];
    SystemDefs.FreeHeapString[s];  RETURN
    END;

  HtiToMdi: PUBLIC PROCEDURE [hti: HTIndex] RETURNS [mdi: MDIndex] =
    BEGIN
    limit: MDIndex = LOOPHOLE[Table.Bounds[mdType].size];
    FOR mdi ← FIRST[MDIndex], mdi + SIZE[MDRecord] UNTIL mdi = limit
      DO
      IF hti = mdb[mdi].moduleId THEN RETURN;
      ENDLOOP;
    RETURN [MDNull]
    END;

  FindMdEntry: PUBLIC PROCEDURE [
	id: HTIndex, version: VersionStamp, file: HTIndex]
      RETURNS [mdi: MDIndex] =
    BEGIN
    limit: MDIndex = LOOPHOLE[Table.Bounds[mdType].size];
    duplicate: BOOLEAN ← FALSE;
    FOR mdi ← FIRST[MDIndex], mdi + SIZE[MDRecord] UNTIL mdi = limit
      DO
      IF mdb[mdi].moduleId = id
	THEN
	  BEGIN
	  IF EqStamps[@mdb[mdi].stamp, @version] THEN RETURN;
	  IF mdb[mdi].stamp = AnyVersion
	    THEN
	      BEGIN
	      OpenSymbols[mdi !FileProblem => RESUME [FALSE]];
	      IF EqStamps[@mdb[mdi].stamp, @version] THEN RETURN;
	      END;
	  IF mdb[mdi].stamp # AnyVersion THEN duplicate ← TRUE;
	  END;
      ENDLOOP;
    IF duplicate THEN SIGNAL FileVersionMix[id];
    mdi ← Table.Allocate[mdType, SIZE[MDRecord]];
    mdb[mdi] ← MDRecord[
	stamp: version,
	moduleId: id,
	fileId: file,
	ctx: IncludedCTXNull,
	shared: FALSE,
	exported: FALSE,
	defaultImport: CTXNull,
	file: NullFileIndex];
    RETURN
    END;


  GetSymbolTable: PUBLIC PROCEDURE [mdi: MDIndex] RETURNS [base: SymbolTable.Base] =
    BEGIN
    index: FileIndex;
    OpenSymbols[mdi];
    index ← mdb[mdi].file;
    IF fileTable[index].file = NIL
      THEN  base ← NIL
      ELSE
	BEGIN
	base ← SymbolTable.Acquire[fileTable[index].table];
	IF base.stHandle.versionIdent # SymbolSegment.VersionID
	  THEN
	    BEGIN
	    SymbolTable.Release[base];  base ← NIL;
	    IF SIGNAL FileProblem[mdb[mdi].fileId]
	      THEN
		BEGIN
		SegmentDefs.DeleteFileSegment[
		  SymbolTable.SegmentForTable[fileTable[index].table]];
		fileTable[index] ← NullFileRecord;
		END;
	    END;
	END;
    RETURN
    END;

  FreeSymbolTable: PUBLIC PROCEDURE [base: SymbolTable.Base] =
    BEGIN  SymbolTable.Release[base]  END;


-- low-level file manipulation

  FileRecord: TYPE = RECORD[
    file: SegmentDefs.FileHandle,
    table: SymbolTable.Handle];

  NullFileRecord: FileRecord = FileRecord[NIL, SymbolTable.NullHandle];

  fileTable: DESCRIPTOR FOR ARRAY OF FileRecord;
  lastFile: INTEGER;


  -- file table management

  FileInit: PUBLIC PROCEDURE [self: STRING, version: VersionStamp] =
    BEGIN
    Table.AddNotify[FilePackNotify];
    IF FindMdEntry[HTNull, version, NormalizeFileName[self]] # Symbols.OwnMdi
      THEN ERROR;
    END;

  CreateFileTable: PUBLIC PROCEDURE [size: CARDINAL] =
    BEGIN  OPEN SystemDefs;
    i: CARDINAL;
    fileTable ← DESCRIPTOR [AllocateHeapNode[size*SIZE[FileRecord]], size];
    FOR i IN [0..size) DO fileTable[i] ← NullFileRecord ENDLOOP;
    lastFile ← -1;
    END;

  ExpandFileTable: PROCEDURE =
    BEGIN  OPEN SystemDefs;
    table: DESCRIPTOR FOR ARRAY OF FileRecord;
    i: CARDINAL;
    size: CARDINAL = LENGTH[fileTable] + 2;
    table ← DESCRIPTOR [AllocateHeapNode[size*SIZE[FileRecord]], size];
    FOR i IN [0..LENGTH[fileTable]) DO table[i] ← fileTable[i] ENDLOOP;
    FOR i IN [LENGTH[fileTable]..size) DO table[i] ← NullFileRecord ENDLOOP;
    FreeHeapNode[BASE[fileTable]];
    fileTable ← table;
    END;

  FileReset: PUBLIC PROCEDURE =
    BEGIN
    i: INTEGER;
    SymbolTable.SetCacheSize[0];
    FOR i IN [0..lastFile]
      DO
      SELECT TRUE FROM
	(fileTable[i].table # SymbolTable.NullHandle) =>
	  SegmentDefs.DeleteFileSegment[
	    SymbolTable.SegmentForTable[fileTable[i].table]];
	(fileTable[i].file # NIL AND fileTable[i].file.lock = 0) =>
	  SegmentDefs.ReleaseFile[fileTable[i].file];
	ENDCASE => NULL;
      ENDLOOP;
    SystemDefs.FreeHeapNode[BASE[fileTable]];
    Table.DropNotify[FilePackNotify];
    END;


  -- file setup

  OwnFile: PUBLIC SIGNAL [file: SegmentDefs.FileHandle] = CODE;

  LocateTables: PUBLIC PROCEDURE [nTables: CARDINAL] =
    BEGIN
    n: CARDINAL ← nTables;
    limit: MDIndex = LOOPHOLE[Table.Bounds[mdType].size];

    CheckFile: PROCEDURE [fp: POINTER TO AltoFileDefs.FP, s: STRING] RETURNS [BOOLEAN] =
      BEGIN
      d1: SubStringDescriptor ← [base:s, offset:0, length:s.length];
      d2: SubStringDescriptor;
      mdi: MDIndex;
      FOR mdi ← FIRST[MDIndex], mdi+SIZE[MDRecord] UNTIL mdi = limit
	DO
	SymbolOps.SubStringForHash[@d2, mdb[mdi].fileId];
	IF StringDefs.EquivalentSubStrings[@d1, @d2]
	  THEN
	    BEGIN
	    mdb[mdi].file ← lastFile ← lastFile+1;
	    fileTable[lastFile] ← FileRecord[
		file: SegmentDefs.InsertFile[fp, SegmentDefs.Read],
		table: SymbolTable.NullHandle];
	    IF mdi = OwnMdi THEN SIGNAL OwnFile[fileTable[lastFile].file];
	    n ← n-1;
	    END;
	ENDLOOP;
      RETURN [n = 0]
      END;

    DirectoryDefs.EnumerateDirectory[CheckFile];
    END;


  MakeFileTableEntry: PUBLIC PROCEDURE
	[file: SegmentDefs.FileHandle, table: SymbolTable.Handle]
      RETURNS [FileIndex] =
    BEGIN
    lastFile ← lastFile + 1;
    UNTIL lastFile < LENGTH[fileTable] DO ExpandFileTable[] ENDLOOP;
    fileTable[lastFile] ← FileRecord[file: file, table: table];
    RETURN [lastFile]
    END;

  FillFile: PROCEDURE [mdi: MDIndex] =
    BEGIN
    newFile: FileIndex;
    desc: SubStringDescriptor;
    name: STRING;
    SymbolOps.SubStringForHash[@desc, mdb[mdi].fileId];
    newFile ← lastFile + 1;
    UNTIL newFile < LENGTH[fileTable] DO ExpandFileTable[] ENDLOOP;
    fileTable[newFile] ← NullFileRecord;
    name ← SystemDefs.AllocateHeapString[desc.length];
    StringDefs.AppendSubString[name, @desc];
      BEGIN  OPEN SegmentDefs;
      fileTable[newFile].file ← NewFile[name, Read, OldFileOnly
	! FileNameError, FileError =>
	    IF SIGNAL FileProblem[mdb[mdi].moduleId]
	      THEN CONTINUE
	      ELSE GO TO noEntry];
      lastFile ← newFile;
      EXITS
	noEntry => newFile ← NullFileIndex;
      END;
    SystemDefs.FreeHeapString[name];
    mdb[mdi].file ← newFile;
    END;


  OpenSymbols: PROCEDURE [mdi: MDIndex] =
    BEGIN
    index: FileIndex;
    symbolSeg, headerSeg: SegmentDefs.FileSegmentHandle ← NIL;

    DeleteHeader: PROCEDURE =
      BEGIN
      IF headerSeg # NIL
	THEN
	  BEGIN OPEN SegmentDefs;
	  Unlock[headerSeg];  DeleteFileSegment[headerSeg];  headerSeg ← NIL;
	  END;
      END;

    Fail: PROCEDURE [voidEntry: BOOLEAN] =
      BEGIN
      IF voidEntry
	THEN
	  BEGIN
	  SELECT TRUE FROM
	    (headerSeg # NIL) =>  DeleteHeader[];
	    (fileTable[index].file.lock = 0) =>
	      SegmentDefs.ReleaseFile[fileTable[index].file];
	    ENDCASE =>  NULL;
	  fileTable[index].file ← NIL;
	  END
	ELSE  DeleteHeader[];
      END;

    IF mdb[mdi].file = NullFileIndex THEN FillFile[mdi];
    index ← mdb[mdi].file;
    IF index # NullFileIndex AND fileTable[index].table = SymbolTable.NullHandle
     AND fileTable[index].file # NIL
      THEN
	BEGIN  OPEN SegmentDefs;
	ENABLE
	  BEGIN
	  UNWIND => NULL;
	  ANY => GO TO badFile
	  END;
	bcd: POINTER TO BcdDefs.BCD;
	bcdPages: CARDINAL;
	mtb, ftb, sgb: Table.Base;
	mti: BcdDefs.MTIndex;
	sSeg: BcdDefs.SGIndex;
	nString: BcdDefs.NameString;
	d1, d2: SubStringDescriptor;
	version: VersionStamp;
	bcdPages ← 1;
	headerSeg ← NewFileSegment[fileTable[index].file, 1, bcdPages, Read];
	  DO
	  SwapIn[headerSeg];  bcd ← FileSegmentAddress[headerSeg];
	  IF bcdPages = bcd.nPages THEN EXIT;
	  bcdPages ← bcd.nPages;
	  Unlock[headerSeg];  SwapOut[headerSeg];
	  MoveFileSegment[headerSeg, 1, bcdPages];
	  ENDLOOP;
	IF bcd.versionident # BcdDefs.VersionID OR bcd.nConfigs > 1
	  THEN GO TO badFile;
	SymbolOps.SubStringForHash[@d1, mdb[mdi].moduleId];
	nString ← LOOPHOLE[bcd + bcd.ssOffset];
	d2.base ← @nString.string;
	ftb ← LOOPHOLE[bcd + bcd.ftOffset];
	mtb ← LOOPHOLE[bcd + bcd.mtOffset];  mti ← FIRST[BcdDefs.MTIndex];
	UNTIL mti = bcd.mtLimit
	  DO
	  d2.offset ← mtb[mti].name;
	  d2.length ← nString.size[mtb[mti].name];
	  IF StringDefs.EqualSubStrings[@d1, @d2] THEN EXIT;
	  mti ← mti + (SIZE[BcdDefs.MTRecord] + mtb[mti].frame.length);
	  REPEAT
	    FINISHED => GOTO badFile;
	  ENDLOOP;
	ftb ← LOOPHOLE[bcd + bcd.ftOffset];
	version ← IF mtb[mti].file = BcdDefs.FTSelf
		    THEN bcd.version
		    ELSE ftb[mtb[mti].file].version;
	sgb ← LOOPHOLE[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;
	IF mdb[mdi].stamp # AnyVersion
	 AND ~EqStamps[@mdb[mdi].stamp, @version] THEN GO TO wrongVersion;
	mdb[mdi].stamp ← version;
	symbolSeg ← NewFileSegment[
			  headerSeg.file,
			  sgb[sSeg].base, sgb[sSeg].pages,
			  Read];
	symbolSeg.class ← other;
	fileTable[index].table ← SymbolTable.TableForSegment[symbolSeg];
	DeleteHeader[];
	EXITS
	  badFile =>  Fail[SIGNAL FileProblem[mdb[mdi].moduleId]];
	  wrongVersion =>  Fail[SIGNAL FileVersion[mdb[mdi].moduleId]];
	END;

    END;


  TableForModule: PUBLIC PROCEDURE [mdi: MDIndex] RETURNS [SymbolTable.Handle] =
    BEGIN
    RETURN[fileTable[mdb[mdi].file].table]
    END;

  END.