-- ListUsing.mesa
-- modified by Bruce, 13-Jan-81 11:05:04
-- modified by Sweet, May 16, 1980  9:37 AM
-- modified by Satterthwaite, May 10, 1983 12:58 pm

DIRECTORY
  Ascii: TYPE USING [SP, CR, NUL],
  CommanderOps: TYPE USING [AddCommand, CommandBlockHandle],
  File: TYPE USING [Capability],
  FileSegment: TYPE USING [Pages],
  FileStream: TYPE USING [Create, EndOf, GetCapability],
  Heap: TYPE USING [
    Create, Delete, Error, Expand, FreeNode, FreeString, Handle, MakeNode, MakeString],
  ListerDefs: TYPE USING [
    IncorrectVersion, Load, MultipleModules, NoCode, NoFGT, NoFile,
    NoSymbols, SetRoutineSymbols, WriteFileID, WriteLine, WriteString],
  LongString: TYPE USING [
    AppendChar, AppendString, EquivalentSubStrings, SubString, SubStringDescriptor, UpperCase],
  OSMiscOps: TYPE USING [FindFile],
  OutputDefs: TYPE USING [
    CloseOutput, OpenOutput, PutChar, PutCR, PutLongString, PutLongSubString, outStream],
  Stream: TYPE USING [Delete, GetChar, Handle],
  Symbols: TYPE USING [BTIndex, CTXIndex, HTIndex, ISEIndex, ISENull, MDIndex, SENull],
  SymbolTable: TYPE USING [Acquire, Base, Release];

ListUsing: PROGRAM
  IMPORTS
    CommanderOps, FileStream, Heap, ListerDefs, LongString, OutputDefs,
    OSMiscOps, Stream, SymbolTable = {
  OPEN ListerDefs, OutputDefs, Symbols;
  
  symbols: SymbolTable.Base;
  myHeap: Heap.Handle ← NIL;
  LongSubString: TYPE = LONG POINTER TO LongString.SubStringDescriptor;
  
  Alloc: PROC [nwords: CARDINAL] RETURNS [p: LONG POINTER] = {
    OPEN Heap;
    p ← MakeNode[
      myHeap, nwords
        ! Error => IF type = insufficientSpace THEN {Expand[myHeap, 1]; RETRY}]};
    
  Free: PROC [p: LONG POINTER] = {Heap.FreeNode[myHeap, p]};
    
  AllocString: PROC [nchars: CARDINAL] RETURNS [s: LONG STRING] = {
    OPEN Heap;
    s ← MakeString[
      myHeap, nchars
        ! Error => IF type = insufficientSpace THEN {Expand[myHeap, 1]; RETRY}]};
    
  FreeString: PROC [s: LONG STRING] = {Heap.FreeString[myHeap, s]};
  
  InitHeap: PROC = {IF myHeap = NIL THEN myHeap ← Heap.Create[5]};
    
  EraseHeap: PROC = {Heap.Delete[myHeap]; myHeap ← NIL};
  
  PutVeryLongSubString: PROC [s: LongSubString] = {
    ss: LongString.SubStringDescriptor ← s↑;
    PutLongSubString[@ss]};
    
  StringCompare: PROC [s1, s2: LONG STRING] RETURNS [INTEGER] = {
    c1, c2: CHAR;
    FOR i: CARDINAL IN [0..MIN[s1.length, s2.length]) DO
      c1 ← LongString.UpperCase[s1[i]];
      c2 ← LongString.UpperCase[s2[i]];
      IF c1 < c2 THEN RETURN[-1];
      IF c1 > c2 THEN RETURN[1];
      ENDLOOP;
    RETURN[
      SELECT TRUE FROM
	s1.length < s2.length => -1,
	s1.length > s2.length => 1,
	ENDCASE => 0]};
    
  CompareNames: PROC [n1, n2: LongSubString] RETURNS [INTEGER] = {
    c1, c2: CHAR;
    FOR i: CARDINAL IN [0..MIN[n1.length, n2.length]) DO
      c1 ← LongString.UpperCase[n1.base[n1.offset + i]];
      c2 ← LongString.UpperCase[n2.base[n2.offset + i]];
      SELECT c1 - c2 FROM < 0 => RETURN[-1]; > 0 => RETURN[1]; ENDCASE;
      ENDLOOP;
    SELECT INTEGER[
      n1.length - n2.length] FROM
      < 0 => RETURN[-1];
      > 0 => RETURN[1];
      ENDCASE => RETURN[0]};
    
  SortNames: PROC [na: LONG DESCRIPTOR FOR ARRAY OF LongString.SubStringDescriptor] = {
    j: INTEGER;
    key: LongString.SubStringDescriptor;
    FOR i: CARDINAL IN [1..LENGTH[na]) DO
      key ← na[i];
      j ← i - 1;
      WHILE j >= 0 AND CompareNames[@na[j], @key] > 0 DO
        temp: CARDINAL = j + 1;
	na[temp] ← na[j];
	j ← j - 1;
	ENDLOOP;
      j ← j + 1;
      na[j] ← key;
      ENDLOOP};
    
  GenCtx: PROC [ctx: Symbols.CTXIndex, p: PROC [Symbols.ISEIndex]] = {
    OPEN symbols;
    sei: Symbols.ISEIndex;
    FOR sei ← FirstCtxSe[ctx], NextSe[sei] UNTIL sei = SENull DO p[sei]; ENDLOOP};
    
  PrintUsing: PROC = {
    OPEN Symbols, symbols;
    bti: BTIndex;
    ctx: CTXIndex;
    sei: ISEIndex;
    hti: HTIndex;
    mdi: MDIndex;
    i, n, idir, ndir: CARDINAL;
    first: BOOL ← TRUE;
    desc: LongString.SubStringDescriptor;
    modname: LongString.SubString = @desc;
    desc2: LongString.SubStringDescriptor;
    filename: LongString.SubString = @desc2;
    mname: LongSubString;
    DirRec: TYPE = RECORD [dirname: LongString.SubStringDescriptor, dirsei: ISEIndex];
    da: LONG DESCRIPTOR FOR ARRAY OF DirRec;
    na: LONG DESCRIPTOR FOR ARRAY OF LongString.SubStringDescriptor;
    firstCopiedHash: Symbols.HTIndex;
    
    countids: PROC [sei: ISEIndex] = {
      IF seb[sei].hash < firstCopiedHash THEN n ← n + 1};
      
    insertid: PROC [sei: ISEIndex] = {
      OPEN symbols;
      IF seb[sei].hash < firstCopiedHash THEN {
	ss: LongString.SubStringDescriptor;
	SubStringForName[LOOPHOLE[@ss], seb[sei].hash];
	na[i] ← ss;
	i ← i + 1}};
      
    PutCR[];
    FOR hti IN (0..LENGTH[ht]) DO
      IF ht[hti].ssIndex = ht[hti - 1].ssIndex THEN {
	firstCopiedHash ← hti; EXIT};
      REPEAT FINISHED => firstCopiedHash ← LENGTH[ht];
      ENDLOOP;
    ndir ← 0;
    FOR sei ← FirstCtxSe[stHandle.directoryCtx], NextSe[sei] UNTIL sei = ISENull
      DO ndir ← ndir + 1; ENDLOOP;
    IF ndir = 0 THEN RETURN;
    da ← DESCRIPTOR[Alloc[SIZE[DirRec]*ndir], ndir];
    ndir ← 0;
    FOR sei ← FirstCtxSe[stHandle.directoryCtx], NextSe[sei] UNTIL sei = ISENull
      DO
      i: INTEGER ← ndir - 1;
      SubStringForName[LOOPHOLE[modname], seb[sei].hash];
      WHILE i >= 0 AND CompareNames[@da[i].dirname, modname] > 0 DO
	da[i + 1] ← da[i]; i ← i - 1; ENDLOOP;
      i ← i + 1;  								-- for compiler
      da[i] ← [modname↑, sei];
      ndir ← ndir + 1;
      ENDLOOP;
    FOR idir IN [0..ndir) DO
      mname ← @da[idir].dirname;
      sei ← da[idir].dirsei;
      WITH seb[UnderType[seb[sei].idType]] SELECT FROM
	definition => {
	  isei: ISEIndex;
	  ctx ← defCtx;
	  FOR isei ← FirstCtxSe[stHandle.importCtx], NextSe[isei] UNTIL isei =
	    ISENull DO
	    WITH seb[UnderType[seb[isei].idType]] SELECT FROM
	      definition =>
		WITH ctxb[defCtx] SELECT FROM
		  imported =>
		    IF includeLink = ctx THEN {ctx ← defCtx; EXIT};
		  ENDCASE;
	      ENDCASE;
	    ENDLOOP};
	transfer => {bti ← seb[sei].idInfo; ctx ← bb[bti].localCtx};
	ENDCASE => ERROR;
      n ← 0;
      GenCtx[ctx, countids];
      WITH ctxb[ctx] SELECT FROM
	included => mdi ← module;
	imported => {
	  mdi ← ctxb[includeLink].module;
	  GenCtx[includeLink, countids]};
	ENDCASE => LOOP; -- main body
      IF n > 0 THEN na ← DESCRIPTOR[Alloc[SIZE[LongString.SubStringDescriptor]*n], n];
      IF n = 0 AND ~mdb[mdi].exported THEN LOOP;
      i ← 0;
      GenCtx[ctx, insertid];
      WITH ctxb[ctx] SELECT FROM
	imported => GenCtx[includeLink, insertid];
	ENDCASE;
      IF first THEN PutLongString["DIRECTORY"L] ELSE PutChar[',];
      PutCR[];
      first ← FALSE;
      PutLongString["  "L];
      PutVeryLongSubString[mname];
      SubStringForName[LOOPHOLE[filename], mdb[mdi].fileId];
      FOR j: CARDINAL IN [0..filename.length) DO
	IF filename.base[filename.offset + j] = '. THEN {
	  filename.length ← j; EXIT};
	ENDLOOP;
      IF ~Equivalent[mname, filename] THEN {
	PutLongString[": FROM """L]; PutLongSubString[filename]; PutChar['"]}
      ELSE PutLongString[": TYPE"L];
      PutLongString[" USING ["L];
      IF n > 0 THEN {
	SortNames[na];
	PutVeryLongSubString[@na[0]];
	FOR i IN (0..LENGTH[na]) DO
	  PutLongString[", "L]; PutVeryLongSubString[@na[i]]; ENDLOOP;
	Free[BASE[na]]};
      PutChar[']];
      ENDLOOP;
    Free[BASE[da]];
    PutChar[';];
    PutCR[];
    PutCR[];
    PutCR[];
    RETURN};
    
  Equivalent: PROC [s1, s2: LongSubString] RETURNS [BOOL] = {
    ss1: LongString.SubStringDescriptor ← s1↑;
    ss2: LongString.SubStringDescriptor ← s2↑;
    RETURN [LongString.EquivalentSubStrings[@ss1, @ss2]]};
    
  Item: TYPE = RECORD [
    link: LONG POINTER TO Item, value: LONG STRING, sublink: LONG POINTER TO Item];
  
  Head: LONG POINTER TO Item ← NIL;
  
  CopyString: PROC [old: LONG STRING] RETURNS [copy: LONG STRING] = {
    IF old = NIL THEN RETURN[NIL];
    copy ← AllocString[old.length];
    LongString.AppendString[copy, old]};
    
  MakeItem: PROC [value: LONG STRING, link: LONG POINTER TO Item]
      RETURNS [item: LONG POINTER TO Item] = {
    item ← Alloc[SIZE[Item]];
    item↑ ← [link: link, value: value, sublink: NIL]};
    
  AddItem: PROC [
        value: LONG STRING, list: LONG POINTER TO LONG POINTER TO Item, copyString: BOOL ← TRUE]
      RETURNS [item: LONG POINTER TO Item] = {
    prev: LONG POINTER TO Item ← NIL;
    FOR item ← list↑, item.link UNTIL item = NIL DO
      SELECT StringCompare[
	item.value, value] FROM
	0 => EXIT;
	1 => {
	  item ← MakeItem[IF copyString THEN CopyString[value] ELSE value, item];
	  IF prev = NIL THEN list↑ ← item ELSE prev.link ← item;
	  EXIT};
	ENDCASE;
      prev ← item;
      REPEAT
	FINISHED => {
	  item ← MakeItem[IF copyString THEN CopyString[value] ELSE value, NIL];
	  IF prev = NIL THEN list↑ ← item ELSE prev.link ← item};
      ENDLOOP};
    
  GetToken: PROC [in: Stream.Handle, s: LONG STRING]
      RETURNS [term: CHAR] = {
    s.length ← 0;
    DO
      IF FileStream.EndOf[in] THEN GOTO eof;
      IF (term ← in.GetChar[]) > Ascii.SP THEN EXIT;
      ENDLOOP;
    WHILE term IN ['a..'z] OR term IN ['A..'Z] OR term IN ['0..'9] DO
      LongString.AppendChar[s, term];
      IF FileStream.EndOf[in] THEN GOTO eof;
      term ← in.GetChar[];
      ENDLOOP;
    EXITS eof => term ← Ascii.NUL};
    
  compressing: BOOL ← FALSE;
  list: BOOL ← FALSE;
  
  Compress: PROC [file: STRING] = {
    fh: File.Capability;
    dh: Stream.Handle;
    compressing ← TRUE;
    InitHeap[];
    OpenOutput[file, ".ul$"L];
    fh ← FileStream.GetCapability[outStream];
    ListerDefs.WriteLine["UsingList:"L];
    UsingList[file];
    CloseOutput[];
--  SetFileAccess[fh, Read + Write + Append];
    dh ← FileStream.Create[fh];
    OpenOutput[file, ".ul"L];
    ListerDefs.WriteLine["Compressing:"L];
    CompressIt[dh];
    CloseOutput[];
    Stream.Delete[dh];
    EraseHeap[];
    Head ← NIL;
    compressing ← FALSE};
    
  UsingList: PROC [cmd: STRING] = {
    s: STRING ← [100];
    ch: CHAR;
    cs: Stream.Handle ← FileStream.Create[OSMiscOps.FindFile[cmd]];
    list ← TRUE;
    IF ~compressing THEN InitHeap[];
    UNTIL FileStream.EndOf[cs] DO
      s.length ← 0;
      WHILE ~FileStream.EndOf[cs] AND (ch ← cs.GetChar[]) # '  DO
	LongString.AppendChar[s, ch]; ENDLOOP;
      IF s.length > 0 THEN {
	IF compressing THEN ListerDefs.WriteString["    "L];
	ListerDefs.WriteLine[s];
	Using[s]};
      ENDLOOP;
    Stream.Delete[cs];
    IF ~compressing THEN EraseHeap[];
    list ← FALSE};
    
  Using: PROC [root: STRING] = {
    OPEN LongString;
    i: CARDINAL;
    defs: BOOL ← FALSE;
    bcdFile: STRING ← [100];
    sseg: FileSegment.Pages;
    IF ~list AND ~compressing THEN InitHeap[];
    AppendString[bcdFile, root];
    FOR i IN [0..bcdFile.length) DO
      IF bcdFile[i] = '. THEN EXIT;
      REPEAT FINISHED => AppendString[bcdFile, ".bcd"L];
      ENDLOOP;
    BEGIN
    [symbols: sseg] ← Load[
      bcdFile ! NoFGT => RESUME; NoCode => {defs ← TRUE; RESUME};
      NoSymbols, IncorrectVersion, MultipleModules => GOTO badformat;
      NoFile => GOTO badname];
    symbols ← SymbolTable.Acquire[sseg];
    ListerDefs.SetRoutineSymbols[symbols];
    IF ~compressing THEN OpenOutput[root, ".ul"L];
    WriteFileID[];
    IF symbols.sourceFile # NIL THEN {
      PutLongString["  Source: "L]; PutLongString[symbols.sourceFile]; PutCR[]};
    PrintUsing[];
    SymbolTable.Release[symbols];
    IF ~compressing THEN CloseOutput[];
    IF ~list AND ~compressing THEN EraseHeap[];
    EXITS
      badformat => ListerDefs.WriteString["Bad Format!"L];
      badname => ListerDefs.WriteString["File Not Found!"L];
    END};
    
  CompressIt: PROC [input: Stream.Handle] = {
    OPEN Ascii;
    term: CHAR;
    user: STRING ← [40];
    userCopy: LONG STRING;
    interface: STRING ← [40];
    used: STRING ← [40];
    int: LONG POINTER TO Item;
    DO
      userCopy ← NIL;
      IF (term ← GetToken[input, user]) = NUL THEN EXIT;
      ListerDefs.WriteString["    "L];
      ListerDefs.WriteLine[user];
      UNTIL term = '; OR term = CR AND StringCompare[interface, "DIRECTORY"L] = 0
	DO term ← GetToken[input, interface] ENDLOOP;
      IF term = '; THEN LOOP;
      UNTIL term = '; DO
	term ← GetToken[input, interface];
	UNTIL term = ', OR term = '[ OR term = '; DO
	  term ← GetToken[input, used] ENDLOOP;
	IF term = '; THEN EXIT;
	IF term = '[ THEN {
	  intitem: LONG POINTER TO Item ← AddItem[interface, @Head];
	  item: LONG POINTER TO Item;
	  IF userCopy = NIL THEN userCopy ← CopyString[user];
	  DO
	    term ← GetToken[input, used];
	    item ← AddItem[used, @intitem.sublink];
	    [] ← AddItem[userCopy, @item.sublink, FALSE];
	    IF term = '] THEN EXIT;
	    ENDLOOP;
	  term ← GetToken[input, used]};
	ENDLOOP;
      ENDLOOP;
    FOR int ← Head, int.link UNTIL int = NIL DO
      item, user: LONG POINTER TO Item;
      c: CHAR;
      IF int.sublink = NIL THEN LOOP;
      PutLongString[int.value];
      PutCR[];
      FOR item ← int.sublink, item.link UNTIL item = NIL DO
	PutLongString["  "L];
	PutLongString[item.value];
	PutChar[SP];
	c ← '(;
	FOR user ← item.sublink, user.link UNTIL user = NIL DO
	  PutChar[c]; c ← SP; PutLongString[user.value]; ENDLOOP;
	PutChar[')];
	PutCR[];
	ENDLOOP;
      PutCR[];
      ENDLOOP};
    
  command: CommanderOps.CommandBlockHandle;
  
  command ← CommanderOps.AddCommand["Using", LOOPHOLE[Using], 1];
  command.params[0] ← [type: string, prompt: "Filename"];
  
  command ← CommanderOps.AddCommand["UsingList", LOOPHOLE[UsingList], 1];
  command.params[0] ← [type: string, prompt: "Filename"];
  
  command ← CommanderOps.AddCommand["CompressUsing", LOOPHOLE[Compress], 1];
  command.params[0] ← [type: string, prompt: "Filename"];
  
  }.