-- file FilePack.Mesa
-- last modified by Satterthwaite, June 30, 1980  5:08 PM
-- last modified by Sandman, July 22, 1980  10:05 AM
-- last modified by Bruce, September 10, 1980  6:00 PM

DIRECTORY
  BcdDefs USING [
    MTIndex, MTRecord, SGIndex, VersionStamp, FTSelf, SGNull, VersionID],
  BcdOps USING [BcdBase, NameString],
  Copier USING [PurgeMdi],
  Strings USING [
    SubString, SubStringDescriptor, AppendSubString, EqualSubStrings,
    EquivalentSubStrings],
  Segments USING [
    AddModifyProc, BaseFromSegment, DeleteSegment, EnumerateDirectory, FHandle,
    FileFromSegment,
    FileProblem, FileNameProblem, FP, InsertFile, LockFile, SegmentAddress, MoveSegment,
    NewFile, NewSegment, PagesFromSegment, ReleasableFile, ReleaseFile, SHandle, SwapIn,
    SwapOut, Unlock, UnlockFile],
  Storage USING [Node, String, Free, FreeString],
  String USING [AppendChar, AppendString],
  SymbolTable USING [
    Base, Handle, NullHandle,
    Acquire, CacheSize, Release, SegmentForTable, SetCacheSize, TableForSegment],
  Symbols USING [mdType,
    HTIndex, MDRecord, MDIndex, FileIndex,
    HTNull, CTXNull, IncludedCTXNull, OwnMdi, MDNull, NullFileIndex],
  SymbolOps USING [EnterString, SubStringForHash],
  SymbolPack USING [mdLimit, stHandle],
  SymbolSegment USING [VersionID],
  Table USING [Base, Notifier, AddNotify, Allocate, Bounds, DropNotify];

FilePack: PROGRAM
    IMPORTS
      Copier, Strings, Segments, Storage, String,
      SymbolTable, SymbolOps, Table, 
      own: SymbolPack
    EXPORTS Copier = 
  BEGIN
  OPEN Symbols;

  SubStringDescriptor: TYPE = Strings.SubStringDescriptor;

-- tables defining the current symbol table

  mdb: Table.Base;		-- module directory base

  FilePackNotify: Table.Notifier = {mdb ← base[mdType]};
  

-- 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: PROC [v1, v2: LONG POINTER TO VersionStamp] RETURNS [BOOLEAN] = {
    RETURN [v1.time = v2.time AND v1.net = v2.net AND v1.host = v2.host]};


  EnterFile: PUBLIC PROC [id: HTIndex, name: Strings.SubString] RETURNS [HTIndex] = {
    mdi: MDIndex = FindMdEntry[id, AnyVersion, NormalizeFileName[name]];
    RETURN [mdb[mdi].moduleId]};

  NormalizeFileName: PROC [name: Strings.SubString] RETURNS [hti: HTIndex] = {
    char: CHARACTER;
    dot: BOOLEAN ← FALSE;
    s: STRING ← Storage.String[name.length+(".bcd"L).length+1];
    desc: SubStringDescriptor;
    FOR i: CARDINAL IN [name.offset .. name.offset+name.length)
      DO
      SELECT (char ← name.base[i]) FROM
	IN ['A..'Z] =>  char ← char + ('a-'A);
	'. =>  dot ← TRUE;
	ENDCASE;
      String.AppendChar[s, char];
      ENDLOOP;
    IF ~dot THEN String.AppendString[s, ".bcd"L];
    IF s[s.length-1] # '. THEN String.AppendChar[s, '.];
    desc ← [base:s, offset:0, length: s.length];
    hti ← SymbolOps.EnterString[@desc];
    Storage.FreeString[s];  RETURN};

  HtiToMdi: PUBLIC PROC [hti: HTIndex] RETURNS [mdi: MDIndex] = {
    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]};

  FindMdEntry: PUBLIC PROC [id: HTIndex, version: VersionStamp, file: HTIndex]
      RETURNS [mdi: MDIndex] = {
    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 {
	  IF EqStamps[@mdb[mdi].stamp, @version] THEN RETURN;
	  IF mdb[mdi].stamp = AnyVersion
	    THEN {
	      OpenSymbols[mdi ! FileProblem => RESUME [FALSE]];
	      IF EqStamps[@mdb[mdi].stamp, @version] THEN RETURN};
	  IF mdb[mdi].stamp # AnyVersion THEN duplicate ← TRUE};
      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];
    own.mdLimit ← own.mdLimit + SIZE[MDRecord];
    RETURN};


  EnterFileSegment: PUBLIC PROC [
	id: HTIndex, version: VersionStamp, fileSeg: Segments.SHandle]
      RETURNS [mdi: MDIndex] = {
    file: Segments.FHandle = Segments.FileFromSegment[fileSeg];
    i: FileIndex;
    limit: MDIndex = LOOPHOLE[Table.Bounds[mdType].size];
    FOR mdi ← FIRST[MDIndex], mdi + SIZE[MDRecord] UNTIL mdi = limit
      DO
      IF EqStamps[@mdb[mdi].stamp, @version]
       AND (id = HTNull OR id = mdb[mdi].moduleId)
	THEN GO TO Found;
      REPEAT
	Found => NULL;
	FINISHED => {
	  mdi ← Table.Allocate[mdType, SIZE[MDRecord]];
	  mdb[mdi] ← MDRecord[
	    stamp: version,
	    moduleId: id,
	    fileId: HTNull,
	    ctx: IncludedCTXNull,
	    shared: FALSE, exported: FALSE,
	    defaultImport: CTXNull,
	    file: ];
	  own.mdLimit ← own.mdLimit + SIZE[MDRecord]};
      ENDLOOP;
    FOR i IN [0..lastFile]
      DO
      IF fileTable[i].file = file
	THEN {
	  tableSeg: Segments.SHandle = SymbolTable.SegmentForTable[fileTable[i].table];
	  IF Segments.BaseFromSegment[tableSeg] = Segments.BaseFromSegment[fileSeg] AND
	    Segments.PagesFromSegment[tableSeg] = Segments.PagesFromSegment[fileSeg]
	    THEN GO TO Found};
      REPEAT
	Found => NULL;
	FINISHED => {
	  i ← lastFile ← lastFile + 1;
	  UNTIL lastFile < LENGTH[fileTable] DO ExpandFileTable[] ENDLOOP;
	  fileTable[i] ← [file: file, table: SymbolTable.TableForSegment[fileSeg]];
	  Segments.LockFile[file]};
      ENDLOOP;
    mdb[mdi].file ← i;    RETURN};


  GetSymbolTable: PUBLIC PROC [mdi: MDIndex] RETURNS [base: SymbolTable.Base] = {
    index: FileIndex;
    OpenSymbols[mdi];
    index ← mdb[mdi].file;
    IF fileTable[index].file = NIL
      THEN base ← NIL
      ELSE {
	base ← SymbolTable.Acquire[fileTable[index].table];
	IF base.stHandle.versionIdent # SymbolSegment.VersionID
	  THEN {
	    SymbolTable.Release[base];  base ← NIL;
	    IF SIGNAL FileProblem[mdb[mdi].fileId]
	      THEN {
		size: CARDINAL = SymbolTable.CacheSize[];
		SymbolTable.SetCacheSize[0];	-- clear cache
		Segments.UnlockFile[fileTable[index].file];
		Segments.DeleteSegment[SymbolTable.SegmentForTable[fileTable[index].table]];
		fileTable[index] ← NullFileRecord;
		SymbolTable.SetCacheSize[size]}}};
    RETURN};

  FreeSymbolTable: PUBLIC PROC [base: SymbolTable.Base] = {SymbolTable.Release[base]};


-- low-level file manipulation

  FileRecord: TYPE = RECORD[
    file: Segments.FHandle,
    table: SymbolTable.Handle];

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

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


  -- file table management

  FileInit: PUBLIC PROC [self: STRING, version: VersionStamp] = {
    ss: Strings.SubStringDescriptor ← [base:self, offset:0, length:self.length];
    Table.AddNotify[FilePackNotify];
    IF FindMdEntry[HTNull, version, NormalizeFileName[@ss]] # Symbols.OwnMdi
      THEN ERROR;
    fileTable ← NIL; lastFile ← -1};

  CreateFileTable: PUBLIC PROC [size: CARDINAL] = {
    fileTable ← DESCRIPTOR [Storage.Node[size*SIZE[FileRecord]], size];
    FOR i: FileIndex IN [0..size) DO fileTable[i] ← NullFileRecord ENDLOOP;
    lastFile ← -1};

  ExpandFileTable: PROC = {
    table: DESCRIPTOR FOR ARRAY OF FileRecord;
    i: FileIndex;
    size: CARDINAL = LENGTH[fileTable] + 2;
    table ← DESCRIPTOR [Storage.Node[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;
    Storage.Free[BASE[fileTable]];
    fileTable ← table};

  FileReset: PUBLIC PROC = {
    SymbolTable.SetCacheSize[0];
    FOR i: INTEGER IN [0..lastFile]
      DO
      IF fileTable[i].table # SymbolTable.NullHandle
	THEN Segments.DeleteSegment[SymbolTable.SegmentForTable[fileTable[i].table]];
      IF fileTable[i].file # NIL
	THEN {
	  Segments.UnlockFile[fileTable[i].file];
	  IF Segments.ReleasableFile[fileTable[i].file]
	    THEN Segments.ReleaseFile[fileTable[i].file]};
      fileTable[i] ← NullFileRecord;
      ENDLOOP;
    Storage.Free[BASE[fileTable]];
    Table.DropNotify[FilePackNotify]};

  ModifyFileTable: PROC [name: STRING, file: Segments.FHandle] RETURNS [ok: BOOLEAN] = {
    mdi: MDIndex;
    limit: MDIndex = LOOPHOLE[Table.Bounds[mdType].size];
    i: INTEGER;
    seg: Segments.SHandle;
    ok ← TRUE;
    IF file = NIL THEN {
      FOR mdi ← OwnMdi, mdi + SIZE[MDRecord] UNTIL mdi = limit DO
	IF (i ← mdb[mdi].file) # NullFileIndex AND fileTable[i].file = NIL THEN {
	  mdb[mdi].file ← NullFileIndex;
	  seg ← SymbolTable.SegmentForTable[fileTable[i].table];
	  IF seg # NIL THEN Segments.DeleteSegment[seg]};
	ENDLOOP;
      RETURN};
    FOR i IN [0..lastFile] DO
      IF fileTable[i].file # file THEN LOOP;
      seg ← SymbolTable.SegmentForTable[fileTable[i].table];
      IF seg # NIL THEN Segments.DeleteSegment[seg];
      Segments.UnlockFile[fileTable[i].file];
      IF (ok ← Segments.ReleasableFile[fileTable[i].file]) THEN
	Segments.ReleaseFile[fileTable[i].file];
      fileTable[i] ← NullFileRecord;
      FOR mdi ← OwnMdi, mdi + SIZE[MDRecord] UNTIL mdi = limit DO
	IF mdb[mdi].file # i THEN LOOP;
	mdb[mdi].file ← NullFileIndex;
	Copier.PurgeMdi[mdi];
	ENDLOOP;
      ENDLOOP};

  -- file setup

  OwnFile: PUBLIC SIGNAL [file: Segments.FHandle] = CODE;

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

    CheckFile: PROC [fp: POINTER TO Segments.FP, s: STRING] RETURNS [BOOLEAN] = {
      d1: SubStringDescriptor ← [base:s, offset:0, length:s.length];
      d2: SubStringDescriptor;
      file: Segments.FHandle;
      FOR mdi: MDIndex ← FIRST[MDIndex], mdi+SIZE[MDRecord] UNTIL mdi = limit
	DO
	SymbolOps.SubStringForHash[@d2, mdb[mdi].fileId];
	IF Strings.EquivalentSubStrings[@d1, @d2]
	  THEN {
	    mdb[mdi].file ← lastFile ← lastFile+1;
	    fileTable[lastFile] ← FileRecord[
		file: (file ← Segments.InsertFile[fp]),
		table: SymbolTable.NullHandle];
	    Segments.LockFile[file];
	    IF mdi = OwnMdi THEN SIGNAL OwnFile[fileTable[lastFile].file];
	    n ← n-1};
	ENDLOOP;
      RETURN [n = 0]};

    Segments.EnumerateDirectory[CheckFile]};


  FillFile: PROC [mdi: MDIndex] = {
    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 ← Storage.String[desc.length];
    Strings.AppendSubString[name, @desc];
      BEGIN
      fileTable[newFile].file ← Segments.NewFile[name
	! Segments.FileNameProblem[], Segments.FileProblem[] =>
	    IF SIGNAL FileProblem[mdb[mdi].moduleId]
	      THEN CONTINUE
	      ELSE GO TO noEntry];
      IF fileTable[newFile].file # NIL
	THEN Segments.LockFile[fileTable[newFile].file]; 
      lastFile ← newFile;
      EXITS
	noEntry => newFile ← NullFileIndex;
      END;
    Storage.FreeString[name];
    mdb[mdi].file ← newFile};


  OpenSymbols: PROC [mdi: MDIndex] = {
    index: FileIndex;
    symbolSeg, headerSeg: Segments.SHandle ← NIL;

    DeleteHeader: PROC = {
      IF headerSeg # NIL
	THEN {Segments.Unlock[headerSeg];  Segments.DeleteSegment[headerSeg];  headerSeg ← NIL}};

    Fail: PROC [voidEntry: BOOLEAN] = {
      IF voidEntry
	THEN {
	  IF headerSeg # NIL THEN DeleteHeader[];
	  IF fileTable[index].file # NIL
	    THEN {
	      Segments.UnlockFile[fileTable[index].file];
	      IF Segments.ReleasableFile[fileTable[index].file]
	        THEN Segments.ReleaseFile[fileTable[index].file]};
	  fileTable[index].file ← NIL}
	ELSE  DeleteHeader[]};

    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 {
	ENABLE {
	  UNWIND => NULL;
	  ANY => GO TO badFile};
	bcd: BcdOps.BcdBase;
	bcdPages: CARDINAL;
	mtb, ftb, sgb: Table.Base;
	mti: BcdDefs.MTIndex;
	sSeg: BcdDefs.SGIndex;
	nString: BcdOps.NameString;
	d1, d2: SubStringDescriptor;
	version: VersionStamp;
	bcdPages ← 1;
	headerSeg ← Segments.NewSegment[fileTable[index].file, 1, bcdPages];
	  DO
	  Segments.SwapIn[headerSeg];  bcd ← Segments.SegmentAddress[headerSeg];
	  IF bcd.versionIdent # BcdDefs.VersionID THEN GO TO badFile;
	  IF bcdPages = bcd.nPages THEN EXIT;
	  bcdPages ← bcd.nPages;
	  Segments.Unlock[headerSeg];  Segments.SwapOut[headerSeg];
	  Segments.MoveSegment[headerSeg, 1, bcdPages];
	  ENDLOOP;
	IF bcd.nConfigs # 0 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 Strings.EqualSubStrings[@d1, @d2] THEN EXIT;
	  mti ← mti + (SIZE[BcdDefs.MTRecord] + mtb[mti].frame.length);
	  REPEAT
	    FINISHED =>
	      IF bcd.nModules = 1 THEN mti ← FIRST[BcdDefs.MTIndex] ELSE 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 ← Segments.NewSegment[
	  Segments.FileFromSegment[headerSeg], sgb[sSeg].base, sgb[sSeg].pages];
	fileTable[index].table ← SymbolTable.TableForSegment[symbolSeg];
	DeleteHeader[];
	EXITS
	  badFile => {
		ENABLE UNWIND => Fail[TRUE];
	    Fail[SIGNAL FileProblem[mdb[mdi].moduleId]]};
	  wrongVersion => {
		ENABLE UNWIND => Fail[TRUE];
	    Fail[SIGNAL FileVersion[mdb[mdi].moduleId]]}}};


  TableForModule: PUBLIC PROC [mdi: MDIndex] RETURNS [SymbolTable.Handle] = {
    RETURN[fileTable[mdb[mdi].file].table]};

  Segments.AddModifyProc[ModifyFileTable];

  END.