-- File: MakeExceptions.mesa
-- Last edit:  Levin at June 9, 1980  4:22 PM.

DIRECTORY
  AltoFileDefs: FROM "AltoFileDefs" USING [CFA],
  ExceptionTableDefs: FROM "ExceptionTableDefs" USING [endMarker, Exception, ExceptionTableEntry,
     ExceptionTableProper, InputEntry, InputEntryPtr, Offset],
  ImageDefs: FROM "ImageDefs" USING [BcdTime, StopMesa],
  InlineDefs: FROM "InlineDefs" USING [COPY],
  IODefs: FROM "IODefs" USING [
    ControlZ, CR, GetInputStream, ReadChar, ReadID, SetEcho, SetInputStream, SP,
    TAB, WriteChar, WriteDecimal, WriteLine, WriteString],
  MiscDefs: FROM "MiscDefs" USING [CommandLineCFA],
  SegmentDefs: FROM "SegmentDefs" USING [FileNameError, InsertFile, Read],
  StreamDefs: FROM "StreamDefs" USING [
    Append, CreateByteStream, GetFA, GetIndex, JumpToFA, ModifyIndex,
    NewByteStream, NewWordStream, Read, SetIndex, StreamError, StreamErrorCode,
    StreamHandle, Write, WriteBlock],
  StringDefs: FROM "StringDefs" USING [AppendChar, AppendString, StringToDecimal],
  SystemDefs: FROM "SystemDefs" USING [
    AllocateHeapNode, AllocateHeapString, AllocatePages, AllocateSegment, FreeHeapNode,
    FreeHeapString, FreePages, FreeSegment],
  TimeDefs: FROM "TimeDefs" USING [AppendDayTime, UnpackDT];

MakeExceptions: PROGRAM
  IMPORTS ImageDefs, InlineDefs, IODefs, MiscDefs, SegmentDefs, StreamDefs, StringDefs,
    SystemDefs, TimeDefs =

BEGIN

OPEN ExceptionTableDefs, IODefs, StreamDefs;

-- Types and Related Constants --

ExceptionTable: TYPE =
  RECORD[
    proper: POINTER TO ExceptionTableProper,
    maxLength: CARDINAL
    ];

ExceptionTablePtr: TYPE = POINTER TO ExceptionTable;

initialInputTableSize: CARDINAL = 200;
initialExceptionTableSize: CARDINAL = 100;
percentToIncrease: [0..100] = 50;

StringPageDescriptor: TYPE =
  RECORD[
    link: StringPage,
    buffer: POINTER TO PACKED ARRAY [0..charsPerPage) OF CHARACTER
    ];

StringPage: TYPE = POINTER TO StringPageDescriptor;

charsPerPage: CARDINAL = 512;

-- Global variables --

inFileName: STRING;
inputStream: StreamHandle;
outFileName: STRING;
outputStream: StreamHandle;

savedInputStream: StreamHandle;
savedEchoState: BOOLEAN;

input: POINTER TO ARRAY OF InputEntry;
inputLength, inputMaxLength: CARDINAL;

tableN: ExceptionTable;
tableX: ExceptionTable;

firstPage, currentPage: StringPage;
stringBase, stringSpaceFF: Offset;
pageFF: [0..charsPerPage];

success: BOOLEAN;

-- Errors --

EndOfInput: ERROR = CODE;
SyntaxError: ERROR = CODE;
BadInputFile: ERROR = CODE;


-- String Space Procedures --

InitializeStringSpace: PROCEDURE =
  BEGIN
  firstPage ← currentPage ← CreateStringSpacePage[];
  stringSpaceFF ← 0;
  PutCharInStringSpace[endMarker];	-- used by omitted entries
  END;

CreateStringSpacePage: PROCEDURE RETURNS[page: StringPage] =
  BEGIN
  page ← SystemDefs.AllocateHeapNode[SIZE[StringPageDescriptor]];
  page↑ ← [link: NIL, buffer: SystemDefs.AllocatePages[1]];
  pageFF ← 0;
  END;

PutCharInStringSpace: PROCEDURE[char: CHARACTER] =
  BEGIN
  IF pageFF >= charsPerPage THEN
    BEGIN
    currentPage.link ← CreateStringSpacePage[];
    currentPage ← currentPage.link;
    END;
  currentPage.buffer[pageFF] ← char;
  pageFF ← pageFF + 1;
  stringSpaceFF ← stringSpaceFF + 1;
  END;

-- Input Procedures --

InitializeInput: PROCEDURE =
  BEGIN
  inputStream ← NewByteStream[inFileName, Read
                              ! SegmentDefs.FileNameError => ERROR BadInputFile];
  SetInputStream[inputStream];
  InitializeInputEntryTable[];
  InitializeExceptionTable[@tableN];
  InitializeExceptionTable[@tableX];
  WriteString["Reading "L];
  WriteString[inFileName];
  WriteString["..."L];
  END;

FinalizeInput: PROCEDURE =
  BEGIN
  inputStream.destroy[inputStream];
  END;

InitializeInputEntryTable: PROCEDURE =
  BEGIN
  input ← SystemDefs.AllocateSegment[initialInputTableSize*SIZE[InputEntry]];
  inputLength ← 0;
  inputMaxLength ← initialInputTableSize;
  END;

InitializeExceptionTable: PROCEDURE[table: ExceptionTablePtr] =
  BEGIN
  i: CARDINAL;
  table.proper ←
    SystemDefs.AllocateSegment[1+initialExceptionTableSize*SIZE[ExceptionTableEntry]];
  FOR i IN [0..initialExceptionTableSize) DO table.proper.entries[i].input ← NIL ENDLOOP;
  table.proper.length ← 0;
  table.maxLength ← initialExceptionTableSize;
  END;

BackUpInput: PROCEDURE =
  BEGIN
  SetIndex[inputStream, ModifyIndex[GetIndex[inputStream], -1]];
  END;

GetChar: PROCEDURE RETURNS[CHARACTER] =
  BEGIN
  char: CHARACTER ← ReadChar[ ! StreamError => HandleStreamTrouble[error]];
  IF char ~= ControlZ THEN RETURN[char];
  UNTIL ReadChar[ ! StreamError => HandleStreamTrouble[error]] = CR DO NULL ENDLOOP;
  RETURN[CR];
  END;

GetRequiredChar: PROCEDURE[c: CHARACTER] =
  BEGIN
  IF GetChar[] ~= c THEN ERROR SyntaxError;
  END;

-- Input Parsing Procedures --

SkipWhiteSpace: PROCEDURE =
  BEGIN
  DO
    SELECT GetChar[] FROM
      SP, TAB, CR => NULL;
      '- =>
        IF GetChar[] = '- THEN UNTIL GetChar[] = CR DO NULL ENDLOOP
        ELSE BEGIN BackUpInput[]; EXIT END;
      ENDCASE => EXIT;
    ENDLOOP;
  BackUpInput[];
  END;

GetInputEntrySlot: PROCEDURE RETURNS [InputEntryPtr] =
  BEGIN
  IF (inputLength ← inputLength + 1) > inputMaxLength THEN
    BEGIN
    newInput: POINTER TO ARRAY OF InputEntry;
    oldLength: CARDINAL ← inputMaxLength;
    inputMaxLength ← inputMaxLength+percentToIncrease*inputMaxLength/100;
    newInput ← SystemDefs.AllocateSegment[inputMaxLength*SIZE[InputEntry]];
    InlineDefs.COPY[from: input, to: newInput, nwords: oldLength*SIZE[InputEntry]];
    SystemDefs.FreeSegment[input];
    input ← newInput;
    END;
  RETURN[@input[inputLength-1]]
  END;

GetExceptionNumber: PROCEDURE RETURNS[Exception] =
  BEGIN
  s: STRING ← [5];
  char: CHARACTER;
  DO
    SELECT char ← GetChar[] FROM
      IN ['0..'9] => StringDefs.AppendChar[s, char];
      ENDCASE => EXIT;
    ENDLOOP;
  BackUpInput[];
  RETURN[StringDefs.StringToDecimal[s]]
  END;

RecordEntryInExceptionTable: PROCEDURE[entry: InputEntryPtr, table: ExceptionTablePtr] =
  BEGIN
  IF entry.exception >= table.maxLength THEN
    BEGIN
    i: CARDINAL;
    newMaxLength: CARDINAL =
      MAX[entry.exception+1, table.maxLength+percentToIncrease*table.maxLength/100];
    newTable: POINTER TO ExceptionTableProper ←
      SystemDefs.AllocateSegment[1+newMaxLength*SIZE[ExceptionTableEntry]];
    InlineDefs.COPY[from: table.proper, to: newTable, nwords: 1+table.proper.length*SIZE[ExceptionTableEntry]];
    SystemDefs.FreeSegment[table.proper];
    table.proper ← newTable;
    FOR i IN [table.maxLength..newMaxLength) DO
      table.proper.entries[i].input ← NIL ENDLOOP;
    table.maxLength ← newMaxLength;
    END;
  IF table.proper.entries[entry.exception].input = NIL THEN
    BEGIN
    table.proper.entries[entry.exception].input ← entry;
    table.proper.length ← MAX[table.proper.length, entry.exception+1];
    END
  ELSE WarnUserOfDuplicate[entry, table];
  END;

RecordEntryInExceptionTables: PROCEDURE[entry: InputEntryPtr] =
  BEGIN
  char: CHARACTER = GetChar[];
  SELECT char FROM
    'N, 'n => RecordEntryInExceptionTable[entry, @tableN];
    'X, 'x => RecordEntryInExceptionTable[entry, @tableX];
    ': =>
      BEGIN
      BackUpInput[];
      RecordEntryInExceptionTable[entry, @tableN];
      RecordEntryInExceptionTable[entry, @tableX];
      END;
    ENDCASE => ERROR SyntaxError;
  END;

SkipToString: PROCEDURE =
  BEGIN
  SkipWhiteSpace[];
  GetRequiredChar[':];
  SkipWhiteSpace[];
  GetRequiredChar['"];
  END;

GetString: PROCEDURE RETURNS[offset: Offset] =
  BEGIN
  char: CHARACTER;
  offset ← stringSpaceFF;
  DO
    SELECT char ← GetChar[] FROM
       '" =>
        IF GetChar[] = '" THEN PutCharInStringSpace['"]
        ELSE BEGIN BackUpInput[]; EXIT END;
       ENDCASE => PutCharInStringSpace[char];
    ENDLOOP;
  PutCharInStringSpace[endMarker];
  END;

GetNextInputEntry: PROCEDURE =
  BEGIN
  entry: InputEntryPtr ← GetInputEntrySlot[];
  entry.exception ← GetExceptionNumber[];
  SkipWhiteSpace[];
  RecordEntryInExceptionTables[entry];
  SkipToString[];
  entry.stringStart ← GetString[];
  END;

-- Error Reporting Procedures --

HandleStreamTrouble: PROCEDURE[error: StreamErrorCode] =
  BEGIN
  SELECT error FROM
    StreamAccess => ERROR EndOfInput;
    ENDCASE => WriteLine["Trouble on input stream -- I give up."L];
  END;

WarnUserOfDuplicate: PROCEDURE[entry: InputEntryPtr, table: ExceptionTablePtr] =
  BEGIN
  WriteChar[CR];
  WriteString["Warning:  duplicate entries for exception #"L];
  WriteDecimal[entry.exception];
  WriteString[" in table '"L];
  WriteChar[IF table = @tableN THEN 'N ELSE 'X];
  WriteLine["'"L];
  END;

-- Output Procedures --

InitializeOutput: PROCEDURE =
  BEGIN
  stringBase ←
    ((tableN.proper.length+tableX.proper.length)*SIZE[output ExceptionTableEntry]+2)*2;
  outputStream ← NewWordStream[outFileName, Read+Write+Append];
  WriteString["Writing "L];
  WriteString[outFileName];
  WriteString["..."L];
  END;

ConvertAndOutputExceptionTable: PROCEDURE[table: ExceptionTablePtr] =
  BEGIN
  i: CARDINAL;
  FOR i IN [0..table.proper.length) DO
    IF table.proper.entries[i].input = NIL THEN table.proper.entries[i].offset ← stringBase
    ELSE
      BEGIN
      entry: InputEntryPtr = table.proper.entries[i].input;
      table.proper.entries[i].offset ← stringBase + entry.stringStart;
      END;
    ENDLOOP;
  [] ← WriteBlock[stream: outputStream, address: table.proper,
      words: SIZE[ExceptionTableProper]+table.proper.length*SIZE[ExceptionTableEntry]];
  END;

OutputStringSpace: PROCEDURE =
  BEGIN
  FOR currentPage ← firstPage, currentPage.link UNTIL currentPage.link = NIL DO
    [] ← WriteBlock[stream: outputStream, address: currentPage.buffer, words: 256];
    ENDLOOP;
  [] ← WriteBlock[stream: outputStream, address: currentPage.buffer, words: (pageFF+1)/2];
  END;

FinalizeOutput: PROCEDURE =
  BEGIN
  outputStream.destroy[outputStream];
  END;

-- Command Processing --

ProcessCommandInput: PROCEDURE =
  BEGIN
  OPEN StringDefs;
  cfa: POINTER TO AltoFileDefs.CFA ← MiscDefs.CommandLineCFA[];
  i: CARDINAL;
  inputStream ← CreateByteStream[SegmentDefs.InsertFile[@cfa.fp, SegmentDefs.Read], Read];
  inFileName ← SystemDefs.AllocateHeapString[40];
  outFileName ← SystemDefs.AllocateHeapString[40];
  JumpToFA[inputStream, @cfa.fa];
  SetInputStream[inputStream];
  BEGIN
  ENABLE StreamError, EndOfInput => CONTINUE;
  SkipWhiteSpace[];
  ReadID[inFileName];
  END;
  GetFA[inputStream, @cfa.fa];
  inputStream.destroy[inputStream];
  SetInputStream[savedInputStream];
  IF inFileName.length = 0 THEN AppendString[inFileName, "LaurelExceptions.txt"L];
  FOR i IN [0..inFileName.length) DO
    IF inFileName[i] = '. THEN EXIT;
    AppendChar[outFileName, inFileName[i]];
    ENDLOOP;
  AppendString[outFileName, ".binary"];
  success ← FALSE;
  END;

-- Miscellaneous --

WriteHerald: PROCEDURE =
  BEGIN
  s: STRING ← [50];
  WriteChar[CR];
  WriteLine["Laurel Exception File Builder."];
  WriteString["Version of "];
  TimeDefs.AppendDayTime[s, TimeDefs.UnpackDT[ImageDefs.BcdTime[]]];
  WriteLine[s];
  WriteChar[CR];
  END;

Cleanup: PROCEDURE =
  BEGIN
  OPEN SystemDefs;
  next: StringPage;
  SetInputStream[savedInputStream];
  [] ← SetEcho[savedEchoState];
  FreeSegment[tableN.proper];
  FreeSegment[tableX.proper];
  FreeSegment[input];
  UNTIL firstPage = NIL DO
    next ← firstPage.link;
    FreePages[firstPage.buffer];
    FreeHeapNode[firstPage];
    firstPage ← next;
    ENDLOOP;
  FreeHeapString[inFileName];
  FreeHeapString[outFileName];
  END;


-- Main program --

savedInputStream ← GetInputStream[];
savedEchoState ← SetEcho[FALSE];

WriteHerald[];
ProcessCommandInput[];

BEGIN

InitializeInput[ ! BadInputFile => GO TO BadFileName];
InitializeStringSpace[];

DO
  SkipWhiteSpace[ ! EndOfInput => EXIT];
  GetNextInputEntry[
      ! EndOfInput => GO TO PrematureEnd;
        SyntaxError => GO TO InputFormatTrouble
      ];
  ENDLOOP;

FinalizeInput[];

WriteDecimal[inputLength];
WriteLine[" exception messages read."];
WriteChar[CR];

InitializeOutput[];

ConvertAndOutputExceptionTable[@tableN];
ConvertAndOutputExceptionTable[@tableX];
OutputStringSpace[];

FinalizeOutput[];

success ← TRUE;

EXITS
  BadFileName =>
    BEGIN
    WriteString["? Can't find file: "];
    WriteLine[inFileName];
    END;
  PrematureEnd =>
    BEGIN
    WriteChar[CR];
    WriteLine["? Unexpected end of input file."L];
    END;
  InputFormatTrouble =>
    BEGIN
    WriteChar[CR];
    WriteString["? Syntax error in input file.  Last valid exception number processed was "L];
    WriteDecimal[input[inputLength-1].exception];
    WriteLine["."L];
    END;
END;

Cleanup[];

IF success THEN WriteLine["Done."]
ELSE
  BEGIN
  WriteString["Type any character to exit..."];
  [] ← ReadChar[];
  END;

ImageDefs.StopMesa[];

END.