-- file StringCompactor.mesa
-- last edited by Satterthwaite, November 2, 1982 11:51 am
  
DIRECTORY
  CharIO: TYPE USING [NumberFormat, PutChar, PutNumber, PutString],
  Environment: TYPE USING [charsPerWord],
  FileStream: TYPE USING [FileByteIndex, Create, EndOf, GetIndex, SetIndex],
  LongStorage: TYPE USING [Node, Free],
  OSMiscOps: TYPE USING [FindFile],
  Stream: TYPE USING [Handle, Delete, GetChar, PutChar, PutWord],
  Strings: TYPE USING [String, AppendString],
  TableCommand: TYPE USING [CreateBCDStream, FinishBcdStream];
  
StringCompactor: PROGRAM
    IMPORTS
      CharIO, FileStream, OSMiscOps, Storage: LongStorage,
      Stream, Strings, TableCommand
    EXPORTS TableCommand = {
  
  charsPerWord: CARDINAL = Environment.charsPerWord;
  StreamIndex: TYPE = FileStream.FileByteIndex;

  SyntaxError: ERROR = CODE;

  CompStrDesc: TYPE = RECORD [offset, length: CARDINAL];
  
  nArrays: CARDINAL;
  nStrings: CARDINAL;
  nChars: CARDINAL;
  nWords: CARDINAL;
  
  in: Stream.Handle;
  
  SLptr: TYPE = LONG POINTER TO SL;
  
  SL: TYPE = RECORD [
    link: SLptr,
    startIndex: StreamIndex,
    length: CARDINAL];
  
  ALptr: TYPE = LONG POINTER TO AL;
  
  AL: TYPE = RECORD [
    link: ALptr,
    name: NL,
    ARRAYindex: StreamIndex,
    needsIndexDef: BOOL,
    headSL, tailSL: SLptr,
    nstrings: CARDINAL];
  
  NL: TYPE = RECORD [startIndex: StreamIndex, length: CARDINAL];
  
  BackUp: PROC [s: Stream.Handle] = {
    FileStream.SetIndex[s, FileStream.GetIndex[s] - 1]};
  
  NextString: PROC [s: SLptr] RETURNS [found: BOOL ← TRUE] = {
    nc: CARDINAL ← 0;
    quoteFound, collectingChars: BOOL ← FALSE;
    DO
      c: CHAR;
      IF FileStream.EndOf[in] THEN ERROR SyntaxError;
      c ← in.GetChar[];
      IF c = '; AND ~collectingChars THEN RETURN [FALSE];
      IF c = '" THEN
	IF quoteFound THEN
	  IF collectingChars THEN {quoteFound ← FALSE; nc ← nc+1} ELSE ERROR
	ELSE
	  IF collectingChars THEN quoteFound ← TRUE
	  ELSE {s.startIndex ← FileStream.GetIndex[in]; collectingChars ← TRUE}
      ELSE
	IF quoteFound THEN {s.length ← nc; BackUp[in]; EXIT}
	ELSE IF collectingChars THEN nc ← nc+1;
      ENDLOOP;
    nStrings ← nStrings+1;
    nChars ← nChars + nc;  nWords ← nWords + StringBody[nc].SIZE};
  
  lastCR: StreamIndex;
  
  AllDone: ERROR = CODE;
  
  NextItem: PROC [a: ALptr] = {
    nc: CARDINAL ← 0;
    state: {
      start, aRray, arRay, arrAy, arraY,
      sTring, stRing, strIng, striNg, strinG, Of, oF, end} ← $start;
    array: BOOL;
  
    DO
      c: CHAR;
      IF FileStream.EndOf[in] THEN ERROR AllDone;
      c ← in.GetChar[];  nc ← nc+1;
      SELECT c FROM
	'A =>
	  state ← SELECT state FROM
  		  $start => $aRray,
		  $arrAy => $arraY,
		  $stRing => $strIng,
		  ENDCASE => $start;
	'R => 
	  state ← SELECT state FROM
		  $aRray => $arRay,
		  $arRay => $arrAy,
		  $stRing => $strIng,
		  ENDCASE => $start;
	'Y =>
	  IF state = $arraY THEN {
	    array ← TRUE; a.ARRAYindex ← FileStream.GetIndex[in]; state ← $end}
	  ELSE state ← $start;
	'S =>
	  IF state = $start THEN {a.name.length ← nc-1; state ← $sTring} ELSE state ← $start;
	'T => state ← IF state = $sTring THEN $stRing ELSE $start;
	'I => state ← IF state = $strIng THEN $striNg ELSE $start;
	'N => state ← IF state = $striNg THEN $strinG ELSE $start;
	'G => IF state = $strinG THEN {array ← FALSE; state ← $end} ELSE state ← $start;
	'\n => {
	  IF state = $end THEN EXIT;
	  lastCR ← FileStream.GetIndex[in];  nc ← 0;  state ← $start};
	IN ['\000..' ] => IF state = $end THEN EXIT ELSE state ← $start;
	ENDCASE => state ← $start;
      ENDLOOP;
    a.name.startIndex ← lastCR;  a.needsIndexDef ← array;
    IF array THEN {
      state ← $Of;
      DO
	c: CHAR;
	IF FileStream.EndOf[in] THEN ERROR SyntaxError;
	c ← in.GetChar[];  nc ← nc+1;
	SELECT c FROM
	  IN ['\000..' ] =>
	    SELECT state FROM
	      $start => state ← $Of;
	      $Of => NULL;
	      $end => EXIT;
	      ENDCASE => state ← $start;
	  'O => state ← IF state = $Of THEN $oF ELSE $start;
	  'F => state ← IF state = $oF THEN $end ELSE $start;
	  ENDCASE => {a.needsIndexDef ← FALSE; state ← $start};
	ENDLOOP;
      a.name.length ← nc};
    CollectStrings[a];
    IF array THEN nArrays ← nArrays + 1};
  
  headAL, tailAL: ALptr;
  
  CollectStrings: PROC [a: ALptr] = { 
    s: SLptr;
    oldnStrings: CARDINAL = nStrings;
    a.headSL ← a.tailSL ← NIL;
    WHILE NextString[s ← AllocateSL[]] DO AppendSL[a, s] ENDLOOP;
    Storage.Free[s];
    a.nstrings ← nStrings - oldnStrings};
  
  
  CollectArrays: PROC = {
    a: ALptr;
    headAL ← tailAL ← NIL;
    nArrays ← nStrings ← nChars ← nWords ← 0;
    lastCR ← 0;
    DO
      NextItem[a ← AllocateAL[] ! AllDone => {Storage.Free[a]; EXIT}];
      AppendAL[a];
      ENDLOOP};
  
  
  AllocateSL: PROC RETURNS [s: SLptr] = {
    s ← Storage.Node[SL.SIZE]; s.link ← NIL; RETURN};
  
  AppendSL: PROC [a: ALptr, s: SLptr] = {
    IF a.tailSL = NIL THEN a.headSL ← s ELSE a.tailSL.link ← s;
    a.tailSL ← s};
  
  
  AllocateAL: PROC RETURNS [a: ALptr] = {
    a ← Storage.Node[AL.SIZE]; a.link ← NIL; RETURN};
  
  AppendAL: PROC [a: ALptr] = {
    IF tailAL = NIL THEN headAL ← a ELSE tailAL.link ← a;
    tailAL ← a};
  
  
  OutStrings: PROC [out: Stream.Handle, compact: BOOL] = {
    charPos: CARDINAL;
    buffer: PACKED ARRAY [0..charsPerWord) OF CHAR;
    byte: [0 .. charsPerWord] ← 0;
  
    FlushBuffer: PROC = {UNTIL byte = 0 DO PutChar['\000] ENDLOOP};
  
    PutChar: PROC [c: CHAR] = {
      buffer[byte] ← c;
      IF (byte ← byte+1) = charsPerWord THEN {
	FOR i: [0..charsPerWord) IN [0..charsPerWord) DO
	  out.PutChar[buffer[i]] ENDLOOP;
	byte ← 0}};
  
    IF compact THEN {out.PutWord[nStrings*CompStrDesc.SIZE+1]; charPos ← 0}
    ELSE {out.PutWord[nStrings]; charPos ← (nStrings+1)*charsPerWord};
    FOR a: ALptr ← headAL, a.link UNTIL a = NIL DO
      FOR s: SLptr ← a.headSL, s.link UNTIL s = NIL DO
        IF compact THEN {
	  out.PutWord[charPos]; out.PutWord[s.length];
	  charPos ← charPos + s.length}
	ELSE {
	  out.PutWord[charPos/charsPerWord];
	  charPos ← charPos + StringBody[s.length].SIZE*charsPerWord};
        ENDLOOP;
      ENDLOOP;
    IF compact THEN {out.PutWord[nChars]; out.PutWord[nChars]};
    FOR a: ALptr ← headAL, a.link UNTIL a = NIL DO
      s: SLptr ← a.headSL;
      UNTIL s = NIL
	DO
	nextS: SLptr = s.link;
	IF ~compact THEN {out.PutWord[s.length]; out.PutWord[s.length]};
        FileStream.SetIndex[in, s.startIndex];
	FOR i: CARDINAL IN [0 .. s.length) DO
	  c: CHAR = in.GetChar[];
	  PutChar[IF c # '" THEN c ELSE in.GetChar[]];
	  ENDLOOP;
	IF ~compact THEN FlushBuffer[];
        Storage.Free[s];  s ← nextS;
        ENDLOOP;
      ENDLOOP;
    FlushBuffer[]};
  
  OutRecordDecl: PROC [
      out: Stream.Handle, formatId: Strings.String, compact, altoCode: BOOL] = {
    OPEN CharIO;
    a: ALptr ← headAL;
    FOR i: CARDINAL IN [0..formatId.length) DO
      IF formatId[i] = '. THEN EXIT; PutChar[out, formatId[i]];
      ENDLOOP;
    PutString[out, ": DEFINITIONS = {
  
  CSRptr: TYPE = "L];
    IF ~altoCode THEN PutString[out, "LONG "L];
    PutString[out, "BASE POINTER TO CompStrRecord;"L];
    PutString[out, IF compact THEN "
  CompStrDesc: TYPE = RECORD [offset, length: CARDINAL];
  
  CompStrRecord: TYPE = RECORD [
    stringOffset: CSRptr RELATIVE POINTER TO StringBody,\n"L
    ELSE "
  StringOffset: TYPE = CSRptr RELATIVE POINTER TO StringBody;
  
  StringRecord: TYPE = RECORD [
    nStrings: CARDINAL,\n"L];
    a ← headAL;
    DO
      nextA: ALptr = a.link;
      nonBlank: BOOL ← FALSE;
      FileStream.SetIndex[in, a.name.startIndex];
      PutString[out, "    "L];
      FOR i: CARDINAL IN [0..a.name.length) DO
        c: CHAR;
        IF a.needsIndexDef AND FileStream.GetIndex[in] = a.ARRAYindex THEN {
	  PutString[out, " [0.."L];
	  PutNumber[out, a.nstrings, NumberFormat[10,FALSE,FALSE,0]];
	  PutChar[out, ')]};
        c ← in.GetChar[];
	IF nonBlank OR c # '  THEN {PutChar[out, c]; nonBlank ← TRUE};
        ENDLOOP;
      PutString[out, IF compact THEN "CompStrDesc"L ELSE "StringOffset"L];
      Storage.Free[a];
      IF (a ← nextA) = NIL THEN EXIT;
      PutChar[out, ',];  PutChar[out, '\n];
      ENDLOOP;
    PutString[out, "];\n\n  }.\n"L]};
  
  
  CompileStrings: PUBLIC PROC [
	inputFile: Strings.String,		-- the source file
	interfaceId: Strings.String,		-- exported interface or "SELF"
	formatId: Strings.String,		-- ASCII record declaration
	moduleId: Strings.String,		-- output file
	compact, altoCode: BOOL]
      RETURNS [CARDINAL, CARDINAL] = {
    t: STRING = [40];
    sStream, rStream: Stream.Handle;
    t.length ← 0;  Strings.AppendString[t, inputFile];
    in ← FileStream.Create[OSMiscOps.FindFile[t, read]];
    CollectArrays[];
    sStream ← TableCommand.CreateBCDStream[
	in: in, modId: moduleId,
	interfaceId: interfaceId,
	altoCode: altoCode];
    OutStrings[sStream, compact];
    TableCommand.FinishBcdStream[]; Stream.Delete[sStream];
    t.length ← 0;  Strings.AppendString[t, formatId];
    rStream ← FileStream.Create[OSMiscOps.FindFile[t, write]];
    OutRecordDecl[rStream, formatId, compact, altoCode];  Stream.Delete[rStream];
    Stream.Delete[in];
    RETURN [nStrings, nChars]};

  }.