-- file PGSControl.Mesa
-- last modified by Satterthwaite, August 29, 1980  1:49 PM

DIRECTORY
  AltoFileDefs: TYPE USING [CFA],
  BcdDefs: TYPE USING [VersionStamp],
  CharIO: TYPE USING [CR, TAB, PutChar, PutString],
  DisplayDefs: TYPE USING [DisplayOn, DisplayOff],
  ImageDefs: TYPE USING [ImageVersion, StopMesa],
  Inline: TYPE USING [BITOR, DIVMOD],
  KeyDefs: TYPE USING [Keys, KeyBits],
  MiscDefs: TYPE USING [CommandLineCFA, DestroyFakeModule],
  PGS1: TYPE USING [Parse],
  PGScondefs: TYPE,
  PGSParseData: TYPE,
  SegmentDefs: TYPE USING [
    Append, DefaultVersion, DestroyFile, FileHandle, FileNameError,
    FileSegmentAddress, FileSegmentHandle, GetFileTimes, InsertFile,
    LockFile, NewFile, OldFileOnly, Read, SwapIn, SwapOut, Unlock,
    UnlockFile, Write],
  StreamDefs: TYPE USING [
    CleanupDiskStream, CreateByteStream, CreateWordStream, GetIndex,
    JumpToFA, ModifyIndex, NewByteStream, NormalizeIndex, ReadBlock,
    SetIndex, StreamError, StreamHandle, StreamIndex, WriteBlock],
  StringDefs: TYPE USING [AppendChar, AppendString, EqualStrings, EquivalentStrings],
  SystemDefs: TYPE USING [
    AllocateHeapNode, AllocatePages, AllocateSegment, FreeHeapNode, FreePages,
    FreeSegment],
  TimeDefs: TYPE USING [AppendDayTime, CurrentDayTime, PackedTime, UnpackDT];

PGSControl: PROGRAM
    IMPORTS
      CharIO, DisplayDefs, ImageDefs, Inline, MiscDefs,
      PGS1, PGScondefs, PGSParseData,
      SegmentDefs, StreamDefs, StringDefs, SystemDefs, TimeDefs
    EXPORTS PGScondefs, PGS1 =
  BEGIN

  eofile, totaltokens, numprod, nextalias: PUBLIC CARDINAL;
  warningslogged: PUBLIC BOOLEAN;
  flags: PUBLIC ARRAY PGScondefs.Options OF BOOLEAN;
  symtab: PUBLIC PGScondefs.Symtab;
  syminfo: PUBLIC PGScondefs.Syminfo;
  aliases: PUBLIC PGScondefs.Aliases;
  tokeninfo: PUBLIC PGScondefs.Tokeninfo;
  prodinfo: PUBLIC PGScondefs.Prodinfo;
  rhschar: PUBLIC PGScondefs.Rhschar;

  slim, tentries, ntentries: PUBLIC CARDINAL;

  bitstrsize: PUBLIC CARDINAL;

  PGSfail: PUBLIC ERROR = CODE;

  outStream: StreamDefs.StreamHandle;

  outeol: PUBLIC PROC [n:CARDINAL] = {
    OPEN CharIO; THROUGH [1..n] DO PutChar[outStream,CR] ENDLOOP};

  outchar: PUBLIC PROC [c:CHARACTER, n:INTEGER]= {
    OPEN CharIO; THROUGH [1..n] DO PutChar[outStream,c] ENDLOOP};

  outstring: PUBLIC PROC [string:STRING] = {
    CharIO.PutString[outStream,string]};

  outtab: PUBLIC PROC = {CharIO.PutChar[outStream,CharIO.TAB]};

  signchar: PUBLIC CHARACTER ← '-;

  outnum: PUBLIC PROC [val:INTEGER, cols:CARDINAL] = {
    i: CARDINAL;
    power, digits: CARDINAL ← 1;
    num: CARDINAL ← ABS[val];
    sign: CARDINAL = IF val<0 THEN 1 ELSE 0;
    WHILE (i←power*10)<=num DO power ← i; digits ← digits+1 ENDLOOP;
    outchar[' , INTEGER[cols-digits-sign]];
    IF sign#0 THEN CharIO.PutChar[outStream,signchar];
    UNTIL power < 1
      DO
      [i,num] ← Inline.DIVMOD[num,power]; CharIO.PutChar[outStream,i+'0];
      power ← power/10;
      ENDLOOP};

  startTime: TimeDefs.PackedTime;

  outtime: PUBLIC PROC = {
    OPEN TimeDefs;
    time: STRING = [20];
    AppendDayTime[time, UnpackDT[startTime]];
    time.length ← time.length-3;
    CharIO.PutString[outStream,time]};

-- storage allocation for PGSscan, PGSlalr, PGStab

  AllocateSegment: PUBLIC PROC [nwords:CARDINAL] RETURNS [POINTER] = {
    RETURN[SystemDefs.AllocateSegment[nwords]]};

  FreeSegment: PUBLIC PROC [base:POINTER] = {SystemDefs.FreeSegment[base]};

  AllocateHeapNode: PUBLIC PROC [nwords:CARDINAL] RETURNS [POINTER] = {
    RETURN[SystemDefs.AllocateHeapNode[nwords]]};

  FreeHeapNode: PUBLIC PROC [base:POINTER] = {SystemDefs.FreeHeapNode[base]};


  LongDes:TYPE = PGScondefs.LongDes;
  LongPointer:TYPE = PGScondefs.LongPointer;

  makearray: PUBLIC PROC [length, width:CARDINAL] RETURNS [LongDes] = {
    n: CARDINAL = length*width;
    new: LongPointer ← AllocateSegment[n];
    FOR i: CARDINAL IN [0..n) DO (new+i)↑ ← 0 ENDLOOP;
    RETURN [DESCRIPTOR[new, length]]};

  expand: PUBLIC PROC [des:LongDes, width, ext:CARDINAL] RETURNS [LongDes] = {
    new, old: LongPointer;
    i: CARDINAL;
    new ← AllocateSegment[(LENGTH[des]+ext)*width];
    old ← BASE[des];
    FOR i IN [0..LENGTH[des]*width) DO (new+i)↑ ← (old+i)↑ ENDLOOP;
    FOR i IN [LENGTH[des]*width..(LENGTH[des]+ext)*width) DO (new+i)↑ ← 0 ENDLOOP;
    FreeSegment[old];
    RETURN [DESCRIPTOR[new, LENGTH[des]+ext]]};


  orcount: PUBLIC CARDINAL;

  orbits: PUBLIC PROC [source,sink:LongPointer] = {
    FOR i: CARDINAL IN [0..PGScondefs.bitstrsize) 
      DO (sink+i)↑ ← Inline.BITOR[(sink+i)↑,(source+i)↑] ENDLOOP;
    orcount ← orcount+1};

-- streams and files

  logstr, sourcestr, outstr, errstr: StreamDefs.StreamHandle;
  tempFile: SegmentDefs.FileHandle;

  sourceName: PUBLIC STRING ← [40];
  sourceVersion: PUBLIC BcdDefs.VersionStamp;
  objectVersion: PUBLIC BcdDefs.VersionStamp;

  rootname: STRING ← [40];
  extension: STRING ← [40];
  binfname: STRING ← [40];
  typename: STRING ← [40];
  modfname: STRING ← [40];
  intfname: STRING ← [40];
 
  CreateTime: PROC [s: StreamDefs.StreamHandle] RETURNS [time: LONG INTEGER] = {
    RETURN [WITH s: s SELECT FROM
      Disk => SegmentDefs.GetFileTimes[s.file].create,
      ENDCASE => 0]};

  DefaultFileName: PROC [name, defaultExtension: STRING] = {
    FOR i: CARDINAL IN [0..name.length) DO IF name[i] = '. THEN RETURN ENDLOOP;
    StringDefs.AppendString[name, defaultExtension]};

  getstream: PROC [dotstring: STRING] RETURNS [StreamDefs.StreamHandle] = {
    OPEN StringDefs, SegmentDefs;
    fileName: STRING ← [40];
    fileName.length ← 0;
    AppendString[fileName, rootname]; AppendString[fileName, dotstring];
    RETURN [StreamDefs.NewByteStream[fileName, Write+Append]]};

  seterrstream: PUBLIC PROC = {
    IF errstr =  NIL
      THEN { 
	outStream ← errstr ← getstream[".pgslog"L];
	outstring[herald]; outstring[" -- "L]; outstring[rootname]; outstring[".pgslog"L];
	outeol[2]}
      ELSE outStream ← errstr};

  setoutstream: PUBLIC PROC [dotstring: STRING] = {
    outStream ← outstr ← getstream[dotstring]};
 
  resetoutstream: PUBLIC PROC = {outStream ← outstr};

  cleanupstreams: PUBLIC PROC = {   -- used for checkout
    OPEN StreamDefs;
    IF outstr # NIL THEN CleanupDiskStream[outstr];
    IF errstr # NIL THEN CleanupDiskStream[errstr]};

  closeoutstream: PUBLIC PROC = {
    IF outstr # NIL THEN {outstr.destroy[outstr]; outstr ← NIL}};

  openwordstream: PUBLIC PROC [scratch: BOOLEAN] = {
    OPEN SegmentDefs;
    outstr ← StreamDefs.CreateWordStream[
      tempFile ← NewFile[binfname,Read+Write+Append,DefaultVersion],
      Write+Append];
    LockFile[tempFile];
    IF ~scratch AND intfname.length # 0
      THEN PGScondefs.WriteBcdHeader[
	outstr,
	binfname,
	IF StringDefs.EqualStrings[intfname,"SELF"L] THEN NIL ELSE intfname,
	alto]};

  closewordstream: PUBLIC PROC = {
    OPEN SegmentDefs;
    closeoutstream[]; UnlockFile[tempFile]; DestroyFile[tempFile]};

  outword: PUBLIC PROC [n: CARDINAL] = {outstr.put[outstr,n]};

  inword: PUBLIC PROC RETURNS [CARDINAL] = {RETURN[outstr.get[outstr]]};

  outblock: PUBLIC PROC [address: POINTER, words: CARDINAL] = {
    [] ← StreamDefs.WriteBlock[outstr, address, words]};


  tB: POINTER TO PACKED ARRAY OF CHARACTER;
  preprocess:BOOLEAN;

  nextbuffer: PUBLIC PROC RETURNS [
      p: POINTER TO PACKED ARRAY OF CHARACTER, c: CARDINAL, last: BOOLEAN] = {
    OPEN PGScondefs;
    words: [0..TextWords];
    bytes: [0..cpw);
    i: CARDINAL;
    words ← StreamDefs.ReadBlock[sourcestr, tB, TextWords];
    bytes ← StreamDefs.GetIndex[sourcestr].byte MOD cpw;
    IF bytes # 0 THEN  words ← words-1;
    i ← words*cpw + bytes;
    IF preprocess
      THEN {
	[] ← StreamDefs.WriteBlock[errstr,tB,words];
	FOR j: CARDINAL IN [words*cpw..i) DO errstr.put[errstr,tB[j]] ENDLOOP};
    RETURN [tB, i, i<TextChars]};

  locateindex: PUBLIC PROC [index: CARDINAL] RETURNS [base: CARDINAL] = {
    OPEN PGScondefs;
    page: CARDINAL;
    page ← index/(pagesize*cpw);
    base ← page*(pagesize*cpw);
    StreamDefs.SetIndex[sourcestr, [page:sourceOrigin.page+page, byte:sourceOrigin.byte]]};

  StreamIndex:TYPE = StreamDefs.StreamIndex;
  
  PrintTextLine: PROC [origin: StreamIndex] RETURNS [start: StreamIndex] = {
    OPEN PGScondefs;
    lineIndex: StreamIndex;
    char: CHARACTER;
    n: [1..100];
    start ← lineIndex ← origin;
    FOR n IN [1..100] UNTIL lineIndex = [0, 0]
      DO lineIndex ← StreamDefs.ModifyIndex[lineIndex, -1];
      StreamDefs.SetIndex[sourcestr, lineIndex];
      IF sourcestr.get[sourcestr] = CR THEN EXIT;
      start ← lineIndex;
      ENDLOOP;
    StreamDefs.SetIndex[sourcestr, start];
    FOR n IN [1..100]
      DO char ← sourcestr.get[sourcestr ! StreamDefs.StreamError => EXIT];
      SELECT char FROM
	CR, ControlZ => EXIT;
	ENDCASE => outchar[char,1];
      ENDLOOP;
    outeol[1];  RETURN};

  sourceOrigin: StreamDefs.StreamIndex;

  ErrorContext: PUBLIC PROC [message: STRING, tokenIndex: CARDINAL] = {
    saveIndex: StreamIndex = StreamDefs.GetIndex[sourcestr];
    origin: StreamIndex = StreamDefs.NormalizeIndex[
	[page: sourceOrigin.page, byte: sourceOrigin.byte+tokenIndex]];
    char: CHARACTER;
    seterrstream[];
    StreamDefs.SetIndex[sourcestr, PrintTextLine[origin]];
    UNTIL StreamDefs.GetIndex[sourcestr] = origin
      DO
      char ← sourcestr.get[sourcestr ! StreamDefs.StreamError => EXIT];
      outchar[IF char = CharIO.TAB THEN CharIO.TAB ELSE ' ,1];
      ENDLOOP;
    outstring["↑ ["L]; outnum[tokenIndex,1];
    outchar['],1];  outeol[1];  outstring[message];
    StreamDefs.SetIndex[sourcestr, saveIndex]};

  CursorBits: TYPE = ARRAY [0..16) OF WORD;
  Cursor: POINTER TO CursorBits = LOOPHOLE[431B];
  savedCursor: CursorBits;

  PGSCursor: CursorBits = 
   [177777b, 177777b, 0, 0,
    160606b, 111111b, 111010b, 111004b, 161302b, 101101b, 101111b, 100606b,
    0, 0, 177777b, 177777b];
 
  advise: PROC = {
    outstring["Errors or warnings logged"L];  outeol[1];
    IF pause THEN {
      BlankCursor: CursorBits = ALL[0];
      QueryCursor: CursorBits = 
	[2000B, 74000B, 140000B, 12767B, 12525B, 53566B, 111113B, 163100B,
 	 0B, 0B, 154000B, 53520B, 62520B, 53360B, 155440B, 140B];
      savedCursor: CursorBits = Cursor↑;
      KeyBits: TYPE = ARRAY [0..SIZE[KeyDefs.KeyBits]-1) OF WORD;
      Keys: POINTER TO KeyBits = LOOPHOLE[KeyDefs.Keys+1];
      savedKeys: KeyBits = Keys↑;
      RTC: POINTER TO
	    MACHINE DEPENDENT RECORD [high: [0..4096), low: [0..16)] =
	  LOOPHOLE[430B];
      savedTime: CARDINAL;
      state: {off, on1, on2};
      Cursor↑ ← BlankCursor;  state ← off;  savedTime ← RTC.high;
	DO
	IF RTC.high # savedTime
	  THEN {
	    SELECT state FROM
	      off => {Cursor↑ ← QueryCursor; state ← on1};
	      on1 => state ← on2;
 	      on2 => {Cursor↑ ← BlankCursor; state ← off};
	      ENDCASE;
	    savedTime ← RTC.high};
	IF Keys↑ # savedKeys THEN EXIT;
	ENDLOOP;
      Cursor↑ ← savedCursor}};

-- processing options

  alto: BOOLEAN ← TRUE;
  pause: BOOLEAN ← TRUE;

-- making an image

  pgsVersion: PUBLIC BcdDefs.VersionStamp;

  tableseghandle: SegmentDefs.FileSegmentHandle;
  herald: STRING ← [50];
  tableseghandle ← MiscDefs.DestroyFakeModule[LOOPHOLE[PGSParseData]].seg;
--ImageDefs.MakeImage["PGS.image"];
  pgsVersion ← LOOPHOLE[ImageDefs.ImageVersion[]];	-- ** bootstrap **
  StringDefs.AppendString[to:herald, from:"Mesa PGS  "];
  TimeDefs.AppendDayTime[herald, TimeDefs.UnpackDT[pgsVersion.time]];
  herald.length ← herald.length - 3;
  
-- * * * * * * HERE IT BEGINS * * * * * *

  BEGIN OPEN SegmentDefs;
  outStream ← logstr ← StreamDefs.NewByteStream["pgs.log"L, Write+Append];
  outstring[herald]; outeol[1];
  END;

  BEGIN OPEN SegmentDefs;
    CR: CHARACTER = CharIO.CR;
    c: CHARACTER;
    ext, ok, scratchexists: BOOLEAN;
    cfa: POINTER TO AltoFileDefs.CFA = MiscDefs.CommandLineCFA[];
    commandStream: StreamDefs.StreamHandle ←
	StreamDefs.CreateByteStream[SegmentDefs.InsertFile[@cfa.fp, Read], Read];
    StreamDefs.JumpToFA[commandStream, @cfa.fa];

    sourceName.length ← rootname.length ← extension.length ← 0; ext ← FALSE;
    UNTIL commandStream.endof[commandStream]
      DO
      IF (c←commandStream.get[commandStream]) # '  AND c # CR THEN EXIT;
      ENDLOOP;
    UNTIL commandStream.endof[commandStream] OR c = '  OR c = CR
      DO
      IF c = '/ THEN GO TO Switches;
      StringDefs.AppendChar[sourceName, c];
      IF c = '. THEN ext ← TRUE;
      StringDefs.AppendChar[IF ext THEN extension ELSE rootname, c];
      c ← commandStream.get[commandStream];
      REPEAT
	Switches => {
	  sense: BOOLEAN ← TRUE;
	  UNTIL commandStream.endof[commandStream] OR
		(c←commandStream.get[commandStream]) = '  OR c = CR
	    DO
	    SELECT c FROM
	      '-, '~ =>  sense ← ~sense;
	      'a, 'A =>  {alto ← sense; sense ← TRUE};
	      'p, 'P =>  {pause ← sense; sense ← TRUE};
	      ENDCASE;
	    ENDLOOP};
      ENDLOOP;
    IF sourceName.length = 0 THEN GO TO NoSource;
    IF ~ext THEN StringDefs.AppendString[sourceName, ".Mesa"L];
    outeol[1];  outstring["Process: "L];
    outstring[sourceName];  outeol[1];
    sourcestr ← StreamDefs.CreateByteStream[
		   NewFile[sourceName, Read, OldFileOnly
		     !FileNameError => {
		       outchar[' ,1]; outstring["File Name Error"L]; GO TO NoSource}],
		  Read];
    DisplayDefs.DisplayOff[black];
    savedCursor ← Cursor↑;  Cursor↑ ← PGSCursor;
    startTime ← TimeDefs.CurrentDayTime[];
    tB ← SystemDefs.AllocatePages[PGScondefs.TextPages];
    warningslogged ← scratchexists ← FALSE;

  binfname.length ← typename.length ← modfname.length ← intfname.length ← 0;
  IF ~ext OR StringDefs.EquivalentStrings[extension, ".Mesa"L] THEN {
    StringDefs.AppendChar[sourceName,'$];
    errstr ← StreamDefs.NewByteStream[sourceName, Write+Append];
    sourceName.length ← sourceName.length-1; --strip $
    tempFile ← NewFile["pgs.scratch"L,Read+Write+Append,DefaultVersion];
    outstr ← StreamDefs.CreateByteStream[tempFile, Write+Append];
    LockFile[tempFile];
    preprocess ← scratchexists ← TRUE;
    outStream ← outstr;
    PGScondefs.Format[binfname,typename,modfname,intfname !PGSfail => GOTO quit];
       -- copies input to sourceName$ (errstr), modified input to pgs.scratch (outstr),
       -- sets up data for printgrammar and optionally the binary and module file names
    outstr.destroy[outstr]; errstr.destroy[errstr]; sourcestr.destroy[sourcestr];

    -- since no rename facility, copy pgs.scratch to sourceName
    sourcestr ← StreamDefs.CreateByteStream[tempFile,Read];
    errstr ← StreamDefs.NewByteStream[sourceName,Write+Append];
    WHILE ~nextbuffer[].last DO NULL ENDLOOP;
    sourceVersion ← [0, 0, CreateTime[errstr]];
    errstr.destroy[errstr]; sourcestr.destroy[sourcestr];

    -- output grammar to pgs.scratch
    outstr ← StreamDefs.CreateByteStream[tempFile,Write+Append];
    outStream ← outstr;
    PGScondefs.PrintGrammar[];
    outstr.destroy[outstr];

    -- connect pgs.scratch to input stream and fix sourceNames
    sourcestr ← StreamDefs.CreateByteStream[tempFile,Read];
    IF modfname.length=0 THEN {
      IF typename.length # 0 THEN StringDefs.AppendString[modfname,typename]
      ELSE {
	StringDefs.AppendString[modfname,rootname];
	StringDefs.AppendString[modfname,"ParseTable"L]}};
    -- derive missing type id (compatibility feature)
    IF typename.length = 0 THEN
      FOR i: CARDINAL IN [0..modfname.length) DO
	IF modfname[i] = '. THEN EXIT;
	StringDefs.AppendChar[typename, modfname[i]];
	ENDLOOP;
    DefaultFileName[modfname,".Mesa"L];
    IF binfname.length=0 THEN {
      StringDefs.AppendString[binfname,rootname];
      StringDefs.AppendString[binfname,"ParseData"L]};
    DefaultFileName[binfname, IF intfname.length=0 THEN ".binary"L ELSE ".bcd"L]}
  ELSE {
    sourceVersion ← [0, 0, CreateTime[sourcestr]];
    StringDefs.AppendString[binfname,rootname];
    StringDefs.AppendString[binfname,".binary"L];
    -- derive type name
      StringDefs.AppendString[typename,rootname];
      StringDefs.AppendString[typename,"ParseTable"L];
    StringDefs.AppendString[modfname,typename];
    StringDefs.AppendString[modfname,".Mesa"L]};

    preprocess ← FALSE;
    outstr ← errstr ← NIL;
    sourceOrigin ← StreamDefs.GetIndex[sourcestr];

-- load table and call first pass here

    BEGIN
    SwapIn[tableseghandle];
    ok ← PGS1.Parse[LOOPHOLE[FileSegmentAddress[tableseghandle]]].nErrors = 0;
    Unlock[tableseghandle]; SwapOut[tableseghandle];
    END;

    SystemDefs.FreePages[tB];
    sourcestr.destroy[sourcestr];  closeoutstream[];
    IF scratchexists THEN {UnlockFile[tempFile]; DestroyFile[tempFile]};

  -- now if no errors generate the tables then package them on request

    IF ok AND (flags[lists] OR flags[printlalr] OR flags[printlr]) THEN {
      ok ← PGScondefs.lalrgen[ ! PGSfail => {ok ← FALSE; CONTINUE}];
      IF ok AND flags[lists] THEN {
        outstr.destroy[outstr];    -- flush output from lalrgen
        outstr ← StreamDefs.CreateWordStream[tempFile,Read];  -- for reinput
        IF ~PGScondefs.tabgen[] THEN closewordstream[]
        ELSE {
	  IF intfname.length # 0 THEN PGScondefs.FixupBcdHeader[];
	  outstr.destroy[outstr]; -- flush tabgen output
	  outstr ← StreamDefs.NewByteStream[modfname, Write+Append];
	  outStream ← outstr;
	  PGScondefs.outmodule[typename,modfname];
	  outstr.destroy[outstr]}}};

    IF errstr # NIL THEN errstr.destroy[errstr];
    outStream ← logstr;
    IF ~ok OR warningslogged THEN advise[];
    Cursor↑ ← savedCursor;  DisplayDefs.DisplayOn[];
    EXITS
    NoSource => NULL;
    quit => {
      outStream ← logstr;
      outeol[1]; outstring["Directives incorrect or out of sequence"L]; outeol[1];
      outstr.destroy[outstr]; UnlockFile[tempFile]; DestroyFile[tempFile]; advise[]};
    END;

  logstr.destroy[logstr];  ImageDefs.StopMesa[];

  END.