-- file ListerUtilities.mesa 
-- last edited by Satterthwaite on August 1, 1983 1:55 pm

DIRECTORY
  Alloc: TYPE USING [Base, Handle, Notifier, Selector],
  BcdDefs: TYPE,
  BcdOps: TYPE USING [BcdBase, NameString],
  CharIO: TYPE USING [PutChar, PutNumber, PutString, PutSubString],
  Environment: TYPE USING [PageCount],
  Exec: TYPE USING [w],
  File: TYPE USING [Capability, nullCapability],
  FileSegment: TYPE USING [Pages, nullPages],
  FileStream: TYPE USING [Create],
  Format: TYPE USING [NumberFormat],
  Heap: TYPE USING [systemZone],
  ListerUtil: TYPE USING [],
  OSMiscOps: TYPE USING [FileError, FindFile],
  Space: TYPE USING [
    Handle, nullHandle, virtualMemory, Create, Delete, LongPointer, Map],
  Stream: TYPE USING [
    DeleteProcedure, Handle, Object, PutByteProcedure, defaultObject],
  Strings: TYPE USING [
    AppendChar, AppendString, AppendSubString, EquivalentSubStrings,
    String, SubString, SubStringDescriptor],
  Symbols: TYPE USING [
    bodyType, ctxType, Name, nullName, htType, ISEIndex, mdType,
    SENull, seType, ssType],
  SymbolSegment: TYPE USING [Base, extType, ltType, treeType, Tables],
  SymbolTable: TYPE USING [Base],
  Time: TYPE USING [Append, Unpack],
  TTY: TYPE USING [PutChar];

ListerUtilities: PROGRAM
  IMPORTS 
    CharIO, Exec, FileStream, Heap, OSMiscOps, Space, Stream, Strings, Time, TTY
  EXPORTS Alloc, ListerUtil = { 
  
  UnknownModule: PUBLIC ERROR = CODE;
  version, creator, source: BcdDefs.VersionStamp;
  filename: Strings.String;
  
  symbols: SymbolTable.Base;
  bases: PRIVATE ARRAY SymbolSegment.Tables OF Alloc.Base;
  
  SetRoutineSymbols: PUBLIC PROC [s: SymbolTable.Base] = {
    OPEN s.stHandle;
    symbase: SymbolSegment.Base ← LOOPHOLE[s.stHandle];
    symbols ← s;
    bases[SymbolSegment.treeType] ← symbase + treeBlock.offset;
    bases[Symbols.seType] ← symbase + seBlock.offset;
    bases[Symbols.htType] ← symbase + htBlock.offset;
    bases[Symbols.ssType] ← symbase + ssBlock.offset;
    bases[Symbols.ctxType] ← symbase + ctxBlock.offset;
    bases[Symbols.mdType] ← symbase + mdBlock.offset;
    bases[Symbols.bodyType] ← symbase + bodyBlock.offset;
    bases[SymbolSegment.ltType] ← symbase + litBlock.offset;
    bases[SymbolSegment.extType] ← symbase + extBlock.offset;
    UpdateBases[]};
    
  NotifyLink: TYPE = LONG POINTER TO NotifyNode;
  NotifyNode: TYPE = RECORD [notifier: Alloc.Notifier, link: NotifyLink];
  
  notifyList: NotifyLink ← NIL;
  
  AddNotify: PUBLIC PROC [h: Alloc.Handle, proc: Alloc.Notifier] = {
    p: NotifyLink =
      (Heap.systemZone).NEW[NotifyNode ← [notifier: proc, link: notifyList]];
    notifyList ← p;
    proc[DESCRIPTOR[bases]]};
    
  DropNotify: PUBLIC PROC [h: Alloc.Handle, proc: Alloc.Notifier] = {
    p, q: NotifyLink;
    IF notifyList = NIL THEN RETURN;
    p ← notifyList;
    IF p.notifier = proc THEN notifyList ← p.link
    ELSE {
      DO
	q ← p;
	p ← p.link;
	IF p = NIL THEN RETURN;
	IF p.notifier = proc THEN EXIT
	ENDLOOP;
      q.link ← p.link};
    (Heap.systemZone).FREE[@p]};
    
  UpdateBases: PROC = {
    FOR p: NotifyLink ← notifyList, p.link UNTIL p = NIL DO
      p.notifier[DESCRIPTOR[bases]] ENDLOOP};
    
  Bounds: PUBLIC PROC [h: Alloc.Handle, table: Alloc.Selector]
      RETURNS [base: Alloc.Base, size: CARDINAL] = {
    OPEN symbols.stHandle;
    SELECT table FROM
      SymbolSegment.treeType => RETURN [bases[table], treeBlock.size];
      Symbols.seType => RETURN [bases[table], seBlock.size];
      Symbols.htType => RETURN [bases[table], htBlock.size];
      Symbols.ssType => RETURN [bases[table], ssBlock.size];
      Symbols.ctxType => RETURN [bases[table], ctxBlock.size];
      Symbols.mdType => RETURN [bases[table], mdBlock.size];
      Symbols.bodyType => RETURN [bases[table], bodyBlock.size];
      SymbolSegment.ltType => RETURN [bases[table], litBlock.size];
      SymbolSegment.extType => RETURN [bases[table], extBlock.size];
      ENDCASE => ERROR};
    

  SetFileName: PUBLIC PROC [name, root, extension: Strings.String] = {
    name.length ← 0;
    FOR i: CARDINAL IN [0 .. root.length) DO
      IF root[i] = '. THEN EXIT;
      Strings.AppendChar[name, root[i]];
      ENDLOOP;
    IF extension # NIL THEN {
      Strings.AppendChar[name, '.]; Strings.AppendString[name, extension]}};
    
  CreateStream: PUBLIC PROC [name: Strings.String] RETURNS [Stream.Handle] = {
    RETURN [FileStream.Create[OSMiscOps.FindFile[name, write]]]};
    
    
  LoadBcd: PUBLIC PROC [fileId: Strings.String] RETURNS [bcd: FileSegment.Pages] = {
    file: File.Capability;
    file ← OSMiscOps.FindFile[fileId, read ! OSMiscOps.FileError => {GO TO noFile}];
    filename ← fileId;
    bcd ← ReadHeader[file];
    RETURN
    EXITS
      noFile => bcd ← FileSegment.nullPages};
    
  LoadModule: PUBLIC PROC [bcd: FileSegment.Pages, typeId: Strings.String]
      RETURNS [mti: BcdDefs.MTIndex, code, symbols: FileSegment.Pages] = {
    mti ← BcdDefs.MTNull;  code ← symbols ← FileSegment.nullPages;
    IF bcd # FileSegment.nullPages THEN {
      BcdBase: PROC [p: LONG POINTER] RETURNS [BcdDefs.Base] = INLINE {
	RETURN [LOOPHOLE[p, BcdDefs.Base]]};
      bcdSpace: Space.Handle = MapPages[bcd];
      bcdBase: BcdOps.BcdBase = bcdSpace.LongPointer[];
      mtb: BcdDefs.Base = BcdBase[bcdBase + bcdBase.mtOffset];
      ftb: BcdDefs.Base = BcdBase[bcdBase + bcdBase.ftOffset];
      sgb: BcdDefs.Base = BcdBase[bcdBase + bcdBase.sgOffset];
      nString: BcdOps.NameString = LOOPHOLE[bcdBase + bcdBase.ssOffset];

      AcquireFile: PROC [fti: BcdDefs.FTIndex] RETURNS [file: File.Capability] = {
	IF fti = BcdDefs.FTSelf THEN file ← bcd.file
	ELSE {
	  d: Strings.SubStringDescriptor ← [@nString.string, NULL, NULL];
	  fileName: STRING ← [100];
	  fileSpace: Space.Handle;
	  fileBase: BcdOps.BcdBase;
	  d.offset ← ftb[fti].name;  d.length ← nString.size[ftb[fti].name];
	  Strings.AppendSubString[fileName, @d];
	  FOR i: CARDINAL IN [0..fileName.length) DO
	    IF fileName[i] = '. THEN EXIT;
	    REPEAT
	      FINISHED => Strings.AppendString[fileName, ".bcd"L];
	    ENDLOOP;
	  file ← OSMiscOps.FindFile[fileName, read
	    ! OSMiscOps.FileError => {GO TO NoFile}];
	  fileSpace ← MapPages[[file, [base: 1, pages: 1]]];
	  fileBase ← fileSpace.LongPointer;
	  IF fileBase.versionIdent # BcdDefs.VersionID
	   OR fileBase.version # ftb[fti].version THEN {
	    Space.Delete[fileSpace]; GO TO BadFile};
	  Space.Delete[fileSpace];
	  EXITS
	    NoFile, BadFile => file ← File.nullCapability};
	RETURN};

      d1: Strings.SubStringDescriptor ← [typeId, 0, typeId.length];
      d2: Strings.SubStringDescriptor ← [@nString.string, NULL, NULL];
      mti ← FIRST[BcdDefs.MTIndex];
      UNTIL mti = bcdBase.mtLimit DO
	d2.offset ← mtb[mti].name;  d2.length ← nString.size[mtb[mti].name];
	IF Strings.EquivalentSubStrings[@d1, @d2] THEN EXIT;
	mti ← mti + SIZE[BcdDefs.MTRecord];
	REPEAT
	  FINISHED =>
	    IF bcdBase.nModules = 1 THEN mti ← FIRST[BcdDefs.MTIndex]
	    ELSE {DeleteSpace[bcdSpace]; ERROR UnknownModule};
	ENDLOOP;
      IF ~bcdBase.definitions THEN {
        code.file ← AcquireFile[sgb[mtb[mti].code.sgi].file];
	IF code.file # File.nullCapability THEN
	  code.span ← [sgb[mtb[mti].code.sgi].base, sgb[mtb[mti].code.sgi].pages]};
      IF sgb[mtb[mti].sseg].pages # 0 THEN {
      	symbols.file ← AcquireFile[sgb[mtb[mti].sseg].file];
	IF symbols.file # File.nullCapability THEN
	  symbols.span ← [
	      sgb[mtb[mti].sseg].base,
	      sgb[mtb[mti].sseg].pages + sgb[mtb[mti].sseg].extraPages]};
      DeleteSpace[bcdSpace]};
    RETURN};
    

  MapPages: PUBLIC PROC [pages: FileSegment.Pages] RETURNS [s: Space.Handle] = {
    IF pages = FileSegment.nullPages THEN s ← Space.nullHandle
    ELSE {
      s ← Space.Create[size: pages.span.pages, parent: Space.virtualMemory];
      s.Map[window: [file: pages.file, base: pages.span.base]]};
    RETURN};
    
  DeleteSpace: PUBLIC PROC [s: Space.Handle] = {
    IF s # Space.nullHandle THEN Space.Delete[s]};
    
    
  ReadHeader: PROC [file: File.Capability] RETURNS [
      bcdPages: FileSegment.Pages ← FileSegment.nullPages] = {
    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;
      nPages: CARDINAL ← 8;
      DO
	headerSpace ← Space.Create[size: nPages, parent: Space.virtualMemory];
	headerSpace.Map[window: [file: file, base: 1]];
	bcd ← headerSpace.LongPointer[];
	IF bcd.versionIdent # BcdDefs.VersionID THEN GO TO badFile;
	IF nPages >= bcd.nPages THEN EXIT;
	nPages ← bcd.nPages;
	Space.Delete[headerSpace];  headerSpace ← Space.nullHandle
	ENDLOOP;
      bcdPages ← [file, [1, bcd.nPages]];
      version ← bcd.version;
      creator ← bcd.creator;
      source ← bcd.sourceVersion;
      DeleteHeader[];
      EXITS
	badFile => {DeleteHeader[]; bcdPages ← FileSegment.nullPages}};
    RETURN};


  PutVersionId: PUBLIC PROC [out: Stream.Handle, stamp: BcdDefs.VersionStamp] = {
    OPEN CharIO;
    StampWords: CARDINAL = SIZE[BcdDefs.VersionStamp];
    str: PACKED ARRAY [0..4*StampWords) OF [0..16) = LOOPHOLE[stamp];
    digit: STRING = "0123456789abcdef"L;
    PutChar[out, '"];
    FOR i: NAT IN [0..4*StampWords) DO PutChar[out, digit[str[i]]] ENDLOOP;
    PutString[out, "\" ("L];
    PutTime[out, stamp.time];
    PutString[out, ", "L]; PutMachine[out, stamp];
    PutChar[out, ')]};

  WriteOneVersion: PROC [
      out: Stream.Handle,
      version: LONG POINTER TO BcdDefs.VersionStamp, tag: Strings.String] = {
    OPEN CharIO;
    IF version = NIL THEN RETURN;
    PutString[out, tag];
    PutTime[out, version.time];
    PutString[out, "  on "L];
    PutMachine[out, version↑];
    PutChar[out, '\n]};
    
  PutVersions: PUBLIC PROC [
      out: Stream.Handle,
      version, creator, source: LONG POINTER TO BcdDefs.VersionStamp ← NIL] = {
    WriteOneVersion[out, version, " created "L];
    WriteOneVersion[out, creator, "    creator "L];
    WriteOneVersion[out, source, "    source "L];
    CharIO.PutChar[out, '\n]};
    
  PutTime: PUBLIC PROC [out: Stream.Handle, time: LONG CARDINAL] = {
    s: STRING = [40];
    Time.Append[s, Time.Unpack[[time]]];
    CharIO.PutString[out, s]};
    
  PutMachine: PUBLIC PROC [out: Stream.Handle, stamp: BcdDefs.VersionStamp] = {
    OPEN CharIO;
    octal: Format.NumberFormat = [8, FALSE, FALSE, 1];
    PutNumber[out, stamp.net, octal];  PutChar[out, '#];
    PutNumber[out, stamp.host, octal]; PutChar[out, '#]};
    
  PutFileID: PUBLIC PROC [out: Stream.Handle] = {
    OPEN CharIO;
    PutString[out, filename];
    PutString[out, ", version "L]; PutVersionId[out, version];
    PutString[out, "\n  source  "L];  PutTime[out, source.time];
    PutString[out, "\n  creator "L]; PutVersionId[out, creator];
    PutString[out, "\n\n"L]};
    
  PutName: PUBLIC PROC [out: Stream.Handle, name: Symbols.Name] = {
    OPEN CharIO;
    desc: Strings.SubStringDescriptor;
    s: Strings.SubString = @desc;
    IF name = Symbols.nullName THEN PutString[out, "(anonymous)"L]
    ELSE {symbols.SubStringForName[s, name]; PutSubString[out, s]}};
    
  PutSei: PUBLIC PROC [out: Stream.Handle, sei: Symbols.ISEIndex] = {
    PutName[out, IF sei = Symbols.SENull THEN Symbols.nullName ELSE symbols.seb[sei].hash]};
    
  -- TTY interface
  
  Message: PUBLIC PROC [s: Strings.String] = {
    FOR i: CARDINAL IN [0..s.length) DO (Exec.w).PutChar[s[i]] ENDLOOP};

  ttyObject: Stream.Object ← Stream.defaultObject;
  
  TTYPutByte: Stream.PutByteProcedure = {
    (Exec.w).PutChar[0c + byte]};
    
  TTYReset: Stream.DeleteProcedure = {
    ttyObject ← Stream.defaultObject};
    
  TTYStream: PUBLIC PROC RETURNS [Stream.Handle] = {
    ttyObject.putByte ← TTYPutByte;
    ttyObject.delete ← TTYReset;
    RETURN [@ttyObject]};
     
  }.