-- StreamIO.Mesa  Edited by Sandman on August 1, 1980  10:20 AM
-- Copyright  Xerox Corporation 1979, 1980

DIRECTORY
  IODefs USING [
    ControlA, ControlH, ControlQ, ControlR, ControlV, ControlW, ControlX, CR, DEL,
    ESC, NumberFormat, SP],
  StreamDefs USING [GetDefaultDisplayStream, GetDefaultKey, StreamHandle],
  StringDefs USING [AppendChar, AppendNumber, StringToNumber, SubString];

StreamIO: PROGRAM
  IMPORTS StreamDefs, StringDefs EXPORTS IODefs SHARES IODefs, StreamDefs =PUBLIC

  BEGIN OPEN StreamDefs, IODefs, StringDefs;

  in, out: StreamHandle;
  beginLine, echo: PRIVATE BOOLEAN ← TRUE;

  GetInputStream: PROCEDURE RETURNS [StreamHandle] = BEGIN RETURN[in] END;

  GetOutputStream: PROCEDURE RETURNS [StreamHandle] = BEGIN RETURN[out] END;

  SetInputStream: PROCEDURE [s: StreamHandle] = BEGIN in ← s END;

  SetOutputStream: PROCEDURE [s: StreamHandle] =
    BEGIN out ← s; beginLine ← TRUE; END;

  SetEcho: PROCEDURE [new: BOOLEAN] RETURNS [old: BOOLEAN] =
    BEGIN old ← echo; echo ← new END;

  -- Character operations


  ReadChar: PROCEDURE RETURNS [CHARACTER] = BEGIN RETURN[in.get[in]]; END;

  WriteChar: PROCEDURE [c: CHARACTER] =
    BEGIN out.put[out, c]; beginLine ← c = CR; END;

  -- Reading Strings


  ReadString: PROCEDURE [s: STRING, t: PROCEDURE [CHARACTER] RETURNS [BOOLEAN]] =
    BEGIN WriteChar[ReadEditedString[s, t, TRUE]]; END;

  ReadID: PROCEDURE [s: STRING] =
    BEGIN [] ← ReadEditedString[s, IsAtom, TRUE]; END;

  ReadLine: PROCEDURE [s: STRING] =
    BEGIN [] ← ReadEditedString[s, IsCR, TRUE]; IF echo THEN WriteChar[CR]; END;

  IsCR: PRIVATE PROCEDURE [c: CHARACTER] RETURNS [BOOLEAN] =
    BEGIN RETURN[c = CR] END;

  IsAtom: PRIVATE PROCEDURE [c: CHARACTER] RETURNS [BOOLEAN] =
    BEGIN RETURN[IF c = SP OR c = CR THEN TRUE ELSE FALSE] END;

  Rubout: SIGNAL = CODE;
  LineOverflow: SIGNAL [s: STRING] RETURNS [ns: STRING] = CODE;

  ReadEditedString: PROCEDURE [
    s: STRING, t: PROCEDURE [CHARACTER] RETURNS [BOOLEAN], newstring: BOOLEAN]
    RETURNS [CHARACTER] =
    BEGIN
    c: CHARACTER;
    i: CARDINAL;
    state: {ti, v, li};
    c ← in.get[in];
    IF newstring THEN
      IF c = ESC THEN BEGIN WriteString[s]; c ← in.get[in]; END ELSE s.length ← 0;
    UNTIL t[c] DO
      SELECT c FROM
	DEL => SIGNAL Rubout;
	ControlA, ControlH =>
	  BEGIN
	  IF s.length > 0 THEN
	    BEGIN
	    IF echo THEN
	      WITH out SELECT FROM
		Display => clearChar[out, s[s.length - 1]];
		ENDCASE => out.put[out, c];
	    s.length ← s.length - 1;
	    END;
	  END;
	ControlW, ControlQ =>
	  BEGIN -- text to be backed up is of the form
	  -- ...<li><v><ti>;   the <v> and <ti> are to be removed.
	  state ← ti;
	  FOR i DECREASING IN [0..s.length) DO
	    SELECT s[i] FROM
	      IN ['A..'Z], IN ['a..'z], IN ['0..'9] =>
		IF state = ti THEN state ← v;
	      ENDCASE => IF state = v THEN state ← li;
	    IF state = li THEN GO TO Done;
	    IF echo THEN
	      WITH out SELECT FROM
		Display => clearChar[out, s[i]];
		ENDCASE => out.put[out, ControlA];
	    REPEAT Done => s.length ← i + 1; FINISHED => s.length ← 0;
	    ENDLOOP;
	  END;
	ControlR => IF echo THEN BEGIN WriteChar[CR]; WriteString[s]; END;
	ControlX =>
	  BEGIN
	  IF echo THEN
	    WITH out SELECT FROM
	      Display => clearCurrentLine[out];
	      ENDCASE => out.put[out, c];
	  s.length ← 0;
	  END;
	ControlV =>
	  BEGIN
	  WHILE s.length >= s.maxlength DO s ← SIGNAL LineOverflow[s]; ENDLOOP;
	  s[s.length] ← c ← in.get[in];
	  s.length ← s.length + 1;
	  IF echo THEN WriteChar[c];
	  END;
	ENDCASE =>
	  BEGIN
	  WHILE s.length >= s.maxlength DO s ← SIGNAL LineOverflow[s]; ENDLOOP;
	  s[s.length] ← c;
	  s.length ← s.length + 1;
	  IF echo THEN WriteChar[c];
	  END;
      c ← in.get[in];
      ENDLOOP;
    RETURN[c];
    END;

  -- Writing Strings


  WriteString: PROCEDURE [s: STRING] =
    BEGIN
    i: CARDINAL;
    FOR i IN [0..s.length) DO out.put[out, s[i]]; ENDLOOP;
    IF s.length # 0 THEN beginLine ← s[s.length - 1] = CR;
    END;

  WriteSubString: PROCEDURE [s: StringDefs.SubString] =
    BEGIN
    i: CARDINAL;
    FOR i IN [s.offset..s.offset + s.length) DO out.put[out, s.base[i]]; ENDLOOP;
    IF s.length # 0 THEN beginLine ← s.base[s.offset + s.length - 1] = CR;
    END;

  WriteLine: PROCEDURE [s: STRING] = BEGIN WriteString[s]; WriteChar[CR]; END;

  NewLine: PROCEDURE RETURNS [BOOLEAN] = BEGIN RETURN[beginLine] END;

  --  Numerical i/o


  ReadNumber: PROCEDURE [default: UNSPECIFIED, radix: CARDINAL]
    RETURNS [UNSPECIFIED] =
    BEGIN
    s: STRING ← [10];
    IF radix = 10 AND LOOPHOLE[default, INTEGER] < 0 THEN
      BEGIN default ← -default; s[0] ← '-; s.length ← 1 END;
    AppendNumber[s, default, radix];
    IF radix = 8 THEN AppendChar[s, 'B];
    [] ← ReadEditedString[s, IsAtom, TRUE];
    RETURN[StringToNumber[s, radix]];
    END;

  ReadDecimal: PROCEDURE RETURNS [INTEGER] =
    BEGIN
    s: STRING ← [10];
    [] ← ReadEditedString[s, IsAtom, TRUE];
    RETURN[StringToNumber[s, 10]]
    END;

  ReadOctal: PROCEDURE RETURNS [UNSPECIFIED] =
    BEGIN
    s: STRING ← [10];
    [] ← ReadEditedString[s, IsAtom, TRUE];
    RETURN[StringToNumber[s, 8]]
    END;

  OutNumber: PROCEDURE [
    stream: StreamHandle, val: INTEGER, format: NumberFormat] =
    BEGIN
    i: CARDINAL;
    neg: BOOLEAN ← FALSE;
    fill: CHARACTER ← (IF format.zerofill THEN '0 ELSE ' );
    s: STRING ← [17];
    IF val < 0 AND ~format.unsigned THEN BEGIN val ← -val; neg ← TRUE END;
    AppendNumber[s, val, format.base];
    i ← s.length;
    IF neg THEN
      BEGIN
      i ← i + 1;
      IF format.zerofill THEN BEGIN stream.put[stream, '-]; neg ← FALSE END;
      END;
    THROUGH (i..format.columns] DO stream.put[stream, fill] ENDLOOP;
    IF neg THEN stream.put[stream, '-];
    FOR i IN [0..s.length) DO stream.put[stream, s[i]] ENDLOOP;
    RETURN
    END;

  WriteNumber: PROCEDURE [v: UNSPECIFIED, f: NumberFormat] =
    BEGIN OutNumber[out, v, f]; beginLine ← FALSE; RETURN END;

  WriteDecimal: PROCEDURE [n: INTEGER] =
    BEGIN WriteNumber[n, NumberFormat[10, FALSE, FALSE, 0]]; RETURN END;

  WriteOctal: PROCEDURE [n: UNSPECIFIED] =
    BEGIN
    WriteNumber[n, NumberFormat[8, FALSE, TRUE, 0]];
    IF n ~IN [0..7] THEN WriteChar['B];
    RETURN
    END;

  in ← StreamDefs.GetDefaultKey[ ! ANY => CONTINUE];
  out ← StreamDefs.GetDefaultDisplayStream[ ! ANY => CONTINUE];

  END.