-- file FilePack.mesa
-- last modified by Satterthwaite, February 18, 1983 10:07 am

DIRECTORY
  Alloc: TYPE USING [Handle, Notifier, AddNotify, DropNotify, Top, Words],
  Copier: TYPE USING [],
  FileParms: TYPE USING [ActualId, BindingProc, Ops, nullActual, nullName],
  Strings: TYPE USING [String, SubString, SubStringDescriptor],
  SymbolTable: TYPE USING [
    Base, Handle, nullHandle, voidHandle, Acquire, Forget, Locked, Release],
  Symbols: TYPE USING [
    Base, Name, MDRecord, MDIndex, FileIndex,
    nullName, CTXNull, IncludedCTXNull, OwnMdi, MDNull, nullFileIndex, mdType],
  SymbolOps: TYPE USING [EnterString, SubStringForName],
  SymbolPack: TYPE USING [mdLimit, stHandle],
  SymbolSegment: TYPE USING [VersionID],
  TimeStamp: TYPE USING [Stamp];

FilePack: PROGRAM
    IMPORTS
      Alloc, SymbolTable, SymbolOps, 
      own: SymbolPack
    EXPORTS Copier = { 
  OPEN Symbols;

  zone: UNCOUNTED ZONE ← NIL;
  table: Alloc.Handle ← NIL;

-- tables defining the current symbol table

  mdb: Symbols.Base;		-- module directory base

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

-- included module accounting

  VersionStamp: TYPE = TimeStamp.Stamp;

  FileProblem: PUBLIC SIGNAL [Name] RETURNS [BOOL] = CODE;
  FileVersion: PUBLIC SIGNAL [Name] RETURNS [BOOL] = CODE;
  FileVersionMix: PUBLIC SIGNAL [Name] = CODE;

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


  EnterFile: PUBLIC PROC [formalId, typeId: Name, defaultFile: Strings.String]
      RETURNS [mdi: MDIndex ← MDNull] = {
    
    BindItem: FileParms.BindingProc = {
      IF actual # FileParms.nullActual THEN
        mdi ← FindMdEntry[typeId, actual.version, SymbolOps.EnterString[@actual.locator]]
      ELSE [] ← SIGNAL FileProblem[formalId]};	-- need better error message

    fd, td: Strings.SubStringDescriptor;
    SymbolOps.SubStringForName[@fd, formalId];
    SymbolOps.SubStringForName[@td, typeId];
    fileParms.Binding[fd, td, defaultFile, BindItem];
    RETURN};

  FindMdEntry: PUBLIC PROC [id: Name, version: VersionStamp, file: Name]
      RETURNS [mdi: MDIndex] = {
    limit: MDIndex = table.Top[mdType];
    duplicate: BOOL ← FALSE;
    FOR mdi ← MDIndex.FIRST, mdi + MDRecord.SIZE UNTIL mdi = limit DO
      IF mdb[mdi].moduleId = id THEN {
	IF mdb[mdi].stamp = version THEN RETURN;
	duplicate ← TRUE};
      ENDLOOP;
    IF duplicate THEN SIGNAL FileVersionMix[id];
    mdi ← table.Words[mdType, MDRecord.SIZE];
    mdb[mdi] ← MDRecord[
	stamp: version,
	moduleId: id,
	fileId: file,
	ctx: IncludedCTXNull,
	shared: FALSE, exported: FALSE,
	defaultImport: CTXNull,
	file: nullFileIndex];
    own.mdLimit ← own.mdLimit + MDRecord.SIZE;
    RETURN};


  GetSymbolTable: PUBLIC PROC [mdi: MDIndex] RETURNS [base: SymbolTable.Base] = {
    index: FileIndex;
    OpenSymbols[mdi];
    index ← mdb[mdi].file;
    IF fileTable[index].file = nullHandle.file THEN base ← NIL
    ELSE {
      base ← SymbolTable.Acquire[fileTable[index]];
      IF base.stHandle.versionIdent # SymbolSegment.VersionID THEN {
	SymbolTable.Release[base];  base ← NIL;
	IF SIGNAL FileProblem[mdb[mdi].fileId] THEN GO TO flush}
      ELSE IF base.stHandle.version # mdb[mdi].stamp THEN {
	SymbolTable.Release[base];  base ← NIL;
	IF SIGNAL FileProblem[mdb[mdi].fileId] THEN GO TO flush};
      EXITS
        flush => {
	  SymbolTable.Forget[fileTable[index] ! SymbolTable.Locked => {CONTINUE}];
	  fileParms.Release[fileTable[index]];
	  fileTable[index] ← voidHandle}};
    RETURN};

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


-- low-level file manipulation

  FileHandle: TYPE = SymbolTable.Handle;
  FileTable: TYPE = RECORD[SEQUENCE length: NAT OF FileHandle];
  
  nullHandle: FileHandle = SymbolTable.nullHandle;
  voidHandle: FileHandle = SymbolTable.voidHandle;
  
  fileTable: LONG POINTER TO FileTable;
  lastFile: INTEGER;


  -- file table management

  fileParms: FileParms.Ops;
  
  FileInit: PUBLIC PROC [
      self: FileParms.ActualId,
      ownTable: Alloc.Handle,
      scratchZone: UNCOUNTED ZONE,
      ops: FileParms.Ops] = {
    table ← ownTable;  table.AddNotify[FilePackNotify];
    zone ← scratchZone;
    IF FindMdEntry[nullName, self.version, SymbolOps.EnterString[@self.locator]] # Symbols.OwnMdi
      THEN ERROR;
    fileParms ← ops;  fileTable ← NIL; lastFile ← -1};

  CreateFileTable: PUBLIC PROC [size: CARDINAL] = {
    n: CARDINAL = size+1;	-- allow for ownMdi
    fileTable ← zone.NEW[FileTable[n]];
    FOR i: FileIndex IN [0..n) DO fileTable[i] ← nullHandle ENDLOOP;
    lastFile ← -1};

  ExpandFileTable: PROC = {
    newTable: LONG POINTER TO FileTable;
    i: FileIndex;
    size: CARDINAL = fileTable.length + 2;
    newTable ← zone.NEW[FileTable[size]];
    FOR i IN [0..fileTable.length) DO newTable[i] ← fileTable[i] ENDLOOP;
    FOR i IN [fileTable.length..size) DO newTable[i] ← nullHandle ENDLOOP;
    zone.FREE[@fileTable];
    fileTable ← newTable};

  FileReset: PUBLIC PROC = {
    FOR i: INTEGER IN [0..lastFile] DO
      IF fileTable[i] # nullHandle THEN fileParms.Release[fileTable[i]];
      fileTable[i] ← nullHandle;
      ENDLOOP;
    zone.FREE[@fileTable]; zone ← NIL;
    table.DropNotify[FilePackNotify]; table ← NIL};


  -- file setup

  MdiToFile: PROC [mdi: MDIndex] RETURNS [FileIndex] = {
    IF mdb[mdi].file = nullFileIndex THEN {
      newFile: FileIndex = lastFile + 1;
      UNTIL newFile < fileTable.length DO ExpandFileTable[] ENDLOOP;
      fileTable[newFile] ← nullHandle;
      lastFile ← newFile;
      mdb[mdi].file ← newFile};
    RETURN [mdb[mdi].file]};


  OpenSymbols: PROC [mdi: MDIndex] = {
    index: FileIndex = MdiToFile[mdi];
    IF fileTable[index] = nullHandle THEN {
      d1, d2: Strings.SubStringDescriptor;
      SymbolOps.SubStringForName[@d1, mdb[mdi].moduleId];
      SymbolOps.SubStringForName[@d2, mdb[mdi].fileId];
      fileTable[index] ← fileParms.Acquire[d1, [mdb[mdi].stamp, d2]];
      IF fileTable[index] = nullHandle
       AND (SIGNAL FileProblem[mdb[mdi].moduleId]) THEN
	fileTable[index] ← voidHandle}};


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

 -- mdi bypass
 
  MapSymbols: PUBLIC PROC [id: FileParms.ActualId] RETURNS [base: SymbolTable.Base] = {
    IF id = FileParms.nullActual THEN base ← NIL
    ELSE {
      handle: SymbolTable.Handle = fileParms.Acquire[FileParms.nullName, id];
      IF handle.file = nullHandle.file THEN base ← NIL
      ELSE {
        base ← SymbolTable.Acquire[handle];
        IF base.stHandle.versionIdent # SymbolSegment.VersionID THEN {
	  fileParms.Release[handle]; SymbolTable.Release[base]; base ← NIL}}};
    RETURN};
     
  UnmapSymbols: PUBLIC PROC [SymbolTable.Base] = FreeSymbolTable;
   
  }.